Java 多线程Thread

来源:互联网 发布:初级程序员考试参考书 编辑:程序博客网 时间:2024/06/05 18:52

重点: 

  • java中多线程运行原理
  • 掌握两种线程创建方式
  • 两种创建线程方式好处和弊端
  • 掌握使用Thread类 中获取和设置线程名称的方法
  • 使用匿名内部类创建多线程
  • 描述java中线程池的运行原理
  • 线程安全问题出现的原因
  • 使用同步代码块\同步方法解决线程安全问题
  • 出现死锁的原因
  • wait方法
  • notify方法
  • 线程的五个状态

一、概述

  • 进程: 进程是指正在运行的程序。 确切的来说,当一个程序进入内存开始运行,即变成一个进程。 进程是处于运行状态的程序,并具有一定独立功能。
  • 线程: 线程是进程的一个执行单元。 负责当前进程中程序的执行。一个进程中至少有一个线程,一个进程可以是多线程的。
  • 多线程程序与单线程程序:
    • 单线程程序 : 若有多个任务只能一次执行。
    • 多线程程序 : 若有多个任务,可以同时执行。


二、多线程运行原理

  • 分时调度 : 所有线程轮流使用CPU 的使用权 , 平均分配每个线程的占用CPU时间。
  • 抢占式调度  : 优先让优先值高的线程使用Cpu , 如果线程的优先级相同 , 则随机选择一个 (**线程的随机性**)。
  • java 使用的是抢占式调度。 

三、抢占式调度详解

  • 实际上 , CPU使用抢占式调度模式是在多个线程之间进行着高速的切换。 对于CPU的一个核而言 , 在某一时刻只能执行一个进程 。 而CPU在多个线程之间的切换速度相对于我们而言相当的块 , 看上去是多个线程同时执行。
  • 多线程不能提高程序的执行速度 ,  但是能提高CPU的使用率。

四、主线程

  • JVM启动后 , 必然有一个执行路径(线程)从mian方法开始 , 一直执行到main方法执行结束 , 这个线程在java中 称之为主线程。
  • 示例:
    package com.tj.ThreadTest;public class ThreadTest_01 {public static void main(String[] args) {class_01 c1 = new class_01("催化");class_01 c2 = new class_01("淑芬");c1.show();c2.show();} }class class_01{private String name;class_01(String name){this.name = name;}public void show(){for(int i= 0;i<1000;i++){System.out.println(this.name+","+i);}}}

五、Thread类

  • 构造方法:
    • Thread() :  创建一个新线程。
    • Thread() : 创建一个名为name的线程。
  • 常用函数:
    • start() : 使线程开始执行 , 由JVM调用Thread‘中的run方法。
    • run() : 该线程要之心的操作
    • sleep() : 在指定的毫秒数之内让当前正在执行的线程休眠(暂停执行)。
  • ’创建线程的两种方法: 
    • 将该类声明为Thread类的子类。 该类必须重写Therad类的run方法 。 创建对象 , 开启线程。 run方法相当于主线程的main方法
    • 该类实现Runnable接口:  实现run方法 , 创建Runnable子类对象并传入到某个线程的构造方法中 , 开启线程。

六、创建线程方式  --- 继承Thread类

  • 第一步: 定义一个类 , 继承Thread类
  • 第二步: 重写run方法
  • 第三步:创建子类 对象
  • 第四步: 调用start方法 , 开启线程并执行  , 此时JVM会自动调用run方法。
  • 示例: 
    package com.tj.ThreadTest;public class ThreadTest_02 {public static void main(String[] args) {//创建线程对象ThreadClass_01 ti = new ThreadClass_01("线程1");//开启线程ti.start();}}//使用继承Thread类的方式创建一个线程类class ThreadClass_01 extends Thread{private String name;public ThreadClass_01(String name) {this.name = name;}//重写run方法@Overridepublic void run() {for(int i =0;i<1;i++){System.out.println(name +","+i);//获取当前对象线程System.out.println(Thread.currentThread());System.out.println(Thread.currentThread().getName());}}}

  • 思考: run方法与start方法的区别?
    • 线程对象调用run方法不能开启线程 。 仅仅是对象调用方法 。 
    • 线程对象调用start方法 ,开启线程 , 并让JVM调用run方法在开启的线程中执行。
  • 继承Therad类原理
    • Thread类用于描述线程 , 具备线程应有的功能 。 
    • 创建线程的目的是为了建立程序单独执行的路径, 让多部分代码同时执行 。  也就是说线程的创建并执行 需要给定线程 执行的任务。
    • 程序中的主线程 , 任务定义在main中  。 自定义线程需要执行的任务定义在run中
    • Thread类中本身的run方法并不是我们所期望的 , 所以要重写run方法 , 重新制定线程任务。
  • 多线程图解
    • 多线程执行在栈内存中 , 其实每一个执行线程都有一片属于自己的栈内存空件 , 进行方法的弹栈和压栈。
    • 当线程任务执行完成之后 , 自动释放栈内存 , 当所有的线程执行完毕之后, 进程结束。
  •  获取线程的名称
    • currentThread() : 返回当前正在执行的线程的实例
    • getName() : 获取当前实例的名称
    • 主线程的名称 : main 
    • 自定义线程的名称: Thread -0   ,如果有多个线程时 , 数字顺延。    。。。 Thread -  1 。。。。 
    • 示例:
      //使用继承Thread类的方式创建一个线程类class ThreadClass_01 extends Thread{private String name;public ThreadClass_01(String name) {this.name = name;}//重写run方法@Overridepublic void run() {for(int i =0;i<1;i++){System.out.println(name +","+i);//获取当前对象线程System.out.println(Thread.currentThread());System.out.println(Thread.currentThread().getName());}}}

七、创建线程方式  --- 实现Runnable接口

  • 第一步 :定义一个类 , 实现Runnable接口
  • 第二步 :实现run方法
  • 第三步 :将该类的实例对象传入到线程的构造函数中。
  • 第四步 :开启线程
  • 示例:
    package com.tj.ThreadTest;public class ThreadTest_03 {public static void main(String[] args) {//创建实现Runnable接口的类对象ThreadClass_03 tc = new ThreadClass_03();//将实现实例一参数形式传入到Thread类的构造函数中Thread t1 = new Thread(tc);Thread t2 = new Thread(tc,"线程2");//可以指定该线程的名字//启动线程t1.start();t2.start();}}//创建实现Runnable接口的类class ThreadClass_03 implements Runnable{@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"线程被执行");}}
  • 实现Runnable原理 , 两种方式的区别:
    • 实现Runnable接口 , 避免了单继承的局限性 , 覆盖Runnable接口中run方法 , 将线程任务定义在run方法中。
    • 只有创建Thread类对象时才能创建新的线程 , 线程任务已经被封装在RUnnable接口中 。  所以可以将实现Runnable接口的实例对象以参数的形式传到Thread构造函数中。 这样线程创建时就可以明确指定要运行的线程任务。
  • 实现Runnable接口的好处
    • 避免了单继承的局限性
    • 更加符合面向对象 。  线程分为两部分  : 线程对象 , 线程任务   ;  而继承Thread之后 , 线程对象和线程任务耦合在一起
    • 实现Runnable接口 , 将任务单独分离出类 封装成对象 , 是线程任务解耦。
  • 线程匿名内部类的使用
    • 方式一:创建线程对象 ,直接重写run方法。
    • 方式二:使用匿名内部类实现RUnnable接口后传入THread构造函数。
    • 示例 : 
      package com.tj.ThreadTest;//线程匿名内部类的使用public class ThreadTest_04 {public static void main(String[] args) {//方式一new Thread(){@Overridepublic void run() {System.out.println("线程1开启");}}.start();//方式二new Thread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"开启");}},"线程2").start();}}
     

八、线程池

  • 概述:
    • 可以容纳多个线程的容器 , 其中的线程可以反复使用 。 省去了频繁创建线程对象的操作 。 节省资源。

  • 为什么要使用线程池?
    • 在java中如果每个请求到达都开启一个线程 , 开销是相当大的 , 在实际开发中 , 创建和销毁线程花费的时间和消耗掉的资源都相当大 , 甚至超过实际处理任务请求的时间和资源。
    • 如果JVM里创建了太多的线程 , 可能会使系统由于过度消耗内存或“”切换过度“导致系统资源不足 , 为了防止系统资源不足的情况   , 需要采取一些办法 来限定任何给定时间处理的请求 数目。
    • 宗上述两点 , 所以需要尽可能的减少线程的创建的销毁次数 , 并尽量使用已有的对象进行服务。
    • 线程池主要用于解决线程声明周期开销和系统资源不足的问题。由于请求到来的时候 ,  线程已经存在 , 所以消除了创建线程带来的延时, 立即为请求服务 。 使程序响应更快。 
  • ”使用线程池的方式 ---- 实现Runnable接口
    • 通常线程池都是通过线程工厂创建 , 在调用线程池中的方法和数据 , 通过线程去执行任务 。 
    • 创建线程池
      • Executors线程池创建工厂类
      • ExecutorService 线程池类
      • Furture接口   用来记录线程任务执行完毕后产生的结果  
    • 使用线程池的步骤:
      • 创建线程池对象
      • 创建Runnable子类对象
      • 提交Runnabel子类对象
      • 关闭线程池
    • 示例: 
      package com.tj.ThreadTest;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;/** * 使用线程池方式 --- Runnable * */public class ThreadTest_05 {public static void main(String[] args) {//创建线程池对象,  指定包含几个线程。ExecutorService es = Executors.newFixedThreadPool(3);//创建实现Runnable接口的子类对象ThreadClass_05 t5 = new ThreadClass_05();//提交线程任务es.submit(t5,"周教练");es.submit(t5,"吴教练");es.submit(t5,"正教练");es.submit(t5,"赵教练");es.submit(t5,"王教练");/*注意: submit 方法调用结束后,程序并不终止,因为线程池控制了线程的关闭 *将使用完的线程有归还到线程池中  * *///关闭线程池es.shutdown();}}class ThreadClass_05 implements Runnable{@Overridepublic void run() {System.out.println("我需要一个教练");try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("教练来了"+Thread.currentThread().getName());System.out.println("叫我游泳之后教练回到了游泳池!");}}

  • 使用线程池方式  --- Callable接口
    • Callable 接口 : 与Runnable接口功能相似 。 用类指定线程任务 ,  其中call方法 用来返回线程任务执行后返回的结果   , call方法可以抛出异常。
    • ExecutorService : 线程池类 , 获取一个线程池对象 , 并执行线程的call方法
    • Future接口 : 用来记录线程对象执行完毕之后产生的结果 。 
    • 使用线程池对象的步骤:
      • 创建线程池对线
      • 创建Callable子类对象
      • 提交Callable子类对象
      • 关闭线程池
    • 示例:
      package com.tj.ThreadTest;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;/** * 使用线程池方式 -- callable * */public class ThreadTest_07 {public static void main(String[] args) throws InterruptedException, ExecutionException {//创建线程池ExecutorService es = Executors.newFixedThreadPool(2);//创建县线程任务ThreadDemo_07 t1 = new ThreadDemo_07();//提交线程任务Future<String> submit = es.submit(t1);System.out.println(submit.get());//关闭线程池es.shutdown();}}class ThreadDemo_07 implements Callable<String> {@Overridepublic String call() throws Exception {return "绝对符合国家";}}

    • 线程池练习: 返回两个数相加的结果
      package com.tj.ThreadTest;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;/** * 线程池练习 ,返回两个数相加的结果 * */public class ThreadTest_08 {public static void main(String[] args) throws InterruptedException, ExecutionException {ExecutorService es = Executors.newFixedThreadPool(2);ThreadDemo_08 t1  = new ThreadDemo_08(1, 1);ThreadDemo_08 t2  = new ThreadDemo_08(6, 1);ThreadDemo_08 t3  = new ThreadDemo_08(3, 1);ThreadDemo_08 t4  = new ThreadDemo_08(2, 1);Future<Integer> s1 = es.submit(t1);Future<Integer> s2 = es.submit(t2);Future<Integer> s3 = es.submit(t3);Future<Integer> s4 = es.submit(t4);System.out.println(s1.get());System.out.println(s2.get());System.out.println(s3.get());System.out.println(s4.get());es.shutdown();}}class ThreadDemo_08 implements Callable<Integer>{int a = 0,b = 0;public ThreadDemo_08(int a, int b) {this.a = a;this.b = b;}@Overridepublic Integer call() throws Exception {return a+b;}}

  • 线程同步
    • 同步代码块
      • 同步代码块 中的锁对象可以是任意对象 ,  但是要使多个线程同步时,需要使用同一个锁对象 , 才能保证线程安全。
    • 同步方法
      • 在方法声明上添加 synchronized
      • 同步方法的锁对象就是this
      • 静态同步方法的锁对象是: 类名.class
    • 线程不安全示例:
      package com.tj.ThreadTest;/** * 电影院卖票 * 本场共100张票 * 多个窗口同时卖票 * */public class ThreadTest_09 {public static void main(String[] args) {ThreadDemo_09 t = new ThreadDemo_09();new Thread(t,"窗口1").start();;new Thread(t,"窗口2").start();;new Thread(t,"窗口3").start();;new Thread(t,"窗口4").start();;}}class ThreadDemo_09 implements Runnable{ int ticket = 100;@Overridepublic void run() {//模拟卖票while(ticket>=0){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"正在卖票,余票:"+ --ticket);}}/** * 出现了错误的情况 , 线程不安全 * 窗口4正在卖票,余票:1窗口3正在卖票,余票:-1窗口1正在卖票,余票:0窗口2正在卖票,余票:-2 * */}

    • 同步代码块示例:
      package com.tj.ThreadTest;/** * 同步代码块解决 线程安全问题 * */public class ThreadTest_10 {public static void main(String[] args) {ThreadDemo_10 t = new ThreadDemo_10();new Thread(t,"窗口一").start();new Thread(t,"窗口二").start();new Thread(t,"窗口三").start();}}class ThreadDemo_10 implements Runnable {int  ticket = 10;//创建锁对象Object lock  =new Object();@Overridepublic void run() {synchronized (lock) {while(ticket>0){try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"正在售票,余票"+ --ticket);}}}}

    • 同步方法示例:
      package com.tj.ThreadTest;/** * 同步方法解决线程安全问题 *  * */public class ThreadTest_11 {public static void main(String[] args) {ThreadDemo_11 t = new ThreadDemo_11();new Thread(t,"窗口一").start();new Thread(t,"窗口二").start();new Thread(t,"窗口三").start();}}class ThreadDemo_11 implements Runnable {int  ticket = 10;//同步方法//同步方法的锁对象就是this// 静态同步方法的锁对象的 : 类名.class@Overridepublic  synchronized void run() {while(ticket>0){try {Thread.sleep(50);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"正在售票,余票"+ --ticket);}}}

  • 死锁
    • 同步锁使用的弊端 : 当线程中出现 多个同步(多个锁)时 , 如果同时嵌套了其他的同步 , 容易 引起 一种现象: 程序 无限等待 , 这种现象叫死锁。
    • 示例:
      package com.tj.ThreadTest;import java.util.Random;/** * 线程任务中出现多个同步时 * 发生死锁的情况 * */public class ThreadTest_12 {public static void main(String[] args) {ThreadDemo_12 t = new ThreadDemo_12();new Thread(t).start();new Thread(t).start();}}//定义锁对象class MyLock_12{public static final Object lockA = new Object();public static final Object lockB = new Object();}class ThreadDemo_12 implements Runnable{@Overridepublic void run() {int x = new Random().nextInt(1); // 0~1if(x%2 == 0){synchronized (MyLock_12.lockA) {System.out.println("if-lockA");synchronized (MyLock_12.lockB) {System.out.println("if - lockB");System.out.println("if 大口吃肉");}}}else{synchronized (MyLock_12.lockB) {System.out.println("if-lockB");synchronized (MyLock_12.lockA) {System.out.println("if - lockA");System.out.println("else 大口吃肉");}}}x++;}}

  • Lock接口
    • 常用方法 :
      • Lock() : 获取锁对象 。
      • unlock() : 释放锁
      • 示例:
        package com.tj.ThreadTest;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;/** * 使用Lock接口  * 改进卖票程序 * */public class ThreadTest_13 {public static void main(String[] args) {MyTicket_13 mt = new MyTicket_13();new Thread(mt , "窗口一").start();new Thread(mt , "窗口二").start();//new Thread(mt , "窗口三").start();//new Thread(mt , "窗口四").start();//new Thread(mt , "窗口五").start();//new Thread(mt , "窗口六").start();}}class MyTicket_13 implements Runnable{int ticket = 10;//创建锁对象Lock l = new ReentrantLock();@Overridepublic void run() {//获取锁l.lock();while(ticket>0){try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"正在卖票,余票:" + --ticket);}//释放锁l.unlock();}}

  • 等待唤醒机制
    • 线程之间的通讯 : 多个线程在处理同一个资源 , 但处理的动作不同。
    • 等待唤醒机制 : 通过 一定的手段使各个线程能有效的利用资源 。  
    • 常用方法:
      • wait() : 等待 , 将正在执行的线程释放其执行资格和执行权 ,并 存储在线程池中 。
      • notify() : 唤醒 , 唤醒线程池中被wait的线程 , 一次唤醒一个 , 而且是任意的 。 
      • notifyAll() : 唤醒所有 , 将线程池中所有被wait的线程唤醒。 
      •  这些方法都定义在Object 中  , 因为:使用 时必须明确指定所属的锁 , 而锁又可以是任意对象  , 所以能被任意对象调用的方法一定定义在Object中 。 
      • 示例:
        package com.tj.ThreadTest;/** * 线程中等待唤醒机制实例 * 等待唤醒机制,使多个线程之间有效利用资源。 * 输入线程想Resource 中输入name ,sex ,输出线程从资源中输出 * 1. 档input发现resource中没有数据时 ,开始输入,输入完成后叫outPut输出,如果已有数据 则wait * 2. 档output发现Resource中没有数据时 ,就wait ,有数据时唤醒输出后叫input输入 * */public class ThreadTest_14 {public static void main(String[] args) {//资源对象Resours_14 r = new Resours_14();//任务对象Input in  = new Input(r);Output out = new Output(r);//开启线程new Thread(in).start();new Thread(out).start();}}//模拟资源类class Resours_14{private String name ;private String sex ;private boolean flag = false;public synchronized void in(String name ,String sex ){//如果有值 ,则wait状态   ,等待输出if(flag)try {wait();} catch (InterruptedException e) {e.printStackTrace();}//设置成员变量this.name = name;this.sex = sex;//设置之后 Resource 中有值 将flag置为trueflag = true;//唤醒outputthis.notify();}public synchronized void  out(){//如果没有值 ,则进入等待状态  ,登台输入if(!flag)try {wait();} catch (InterruptedException e) {e.printStackTrace();}//将数据输出System.out.println("姓名:" + name +"性别: "+sex);//改变标记flag = false;//唤醒input  ,进行数据输入this.notify();}}//输入线程任务类class Input implements Runnable{private Resours_14 r ;public Input(Resours_14 r) {this.r = r;}@Overridepublic void run() {int count = 0;while (true){//降低CPU压力  try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}//交替设置资源if(count == 0){r.in("小明", "男");}else{r.in("小花", "女");}//两个数据之间相互切换count = (count +1) %2;}}}//输出线程任务类class Output implements Runnable{private Resours_14 r ;public Output(Resours_14 r) {super();this.r = r;}@Overridepublic void run() {while(true){//降低CPU压力  try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}//输出资源内容r.out();}}}

  • 补充: wait() 与 sleep() 的区别 : 
    • wait() : 释放锁对象 , 释放CPU使用权 , 在休眠时间内能被唤醒 。
    • sleep() : 不释放锁对象 , 释放CPU使用权 , 在休眠时间内不能被唤醒

九、线程安全

  •  如果有多个线程同时运行同一段代码,程序每次运行结果和单线程运行的结果是一样的,而且其他的变量值也与预期是一样的,那么就是线程安全的。

  • 线程的安全问题都是由全局变量和静态变量引起的,每个线程对全局变量和局部变量只有读操作而没有写操作,一般来说这个线程就是安全的;若多个线程同时执行写操作,一般都是需要考虑线程同步,否则的话可能影响线程安全。


  • 示例:

    /** * 线程同步的两种方式 1. 同步代码块 2. 同步方法 示例: 电影院买票 共有100张票 ,三个窗口同时买票 * */public class ThreadDemo_09 {public static void main(String[] args) {new Thread(new ThreadTest_09_01(),"窗口一").start();new Thread(new ThreadTest_09_01(),"窗口二").start();new Thread(new ThreadTest_09_01(),"窗口三").start();}}class ThreadTest_09_01 implements Runnable {private static int ticket = 100;@Overridepublic void run() {while (1 == 1) {if (ticket > 0) {System.out.println(Thread.currentThread().getName() + "出票,余票"+ --ticket);}}}}结果:窗口一出票,余票99窗口三出票,余票98窗口二出票,余票99//与窗口一重复   ,线程不安全窗口二出票,余票95窗口三出票,余票96窗口三出票,余票93窗口三出票,余票92窗口一出票,余票97窗口二出票,余票94窗口一出票,余票91窗口二出票,余票90。。。。

    十、线程同步

    • java中处理线程同步 的两种方式:
      • 同步代码块
      • 同步方法
    • 同步代码块: 在代码块声明上加上synchronized     且要使用同一锁对象才能保证线程同步。.
    • 同步方法   : 在方法属性上加synchronized  。

    十一、守护线程

  • 守护其他线程的执行。

  • 当被守护的线程结束时 , 无论守护线程是否执行完毕则随之结束 。 

  • 只要代码中出现了线程要么是守护线程 , 要么是被守护的线程 。 

  • 如果出现率多个被守护的线程 , 则以最后一个被守护的线程结束为标志而结束 。 

  • gc(垃圾回收机制)本质上就是一盒

  • //设置为守护线程t.setDaemon(true);

  • 十二、 线程的优先级

    • 线程有1~10   10个优先级 。 
    • 数字越大 , 优先级越高 , 理论上优先级越高的线程越容易抢到cpu执行权 , 但是相邻的两个优先级之间差别不大, 至少相差五个优先级才能看出效果 。 
    • 如果不设置优先级 , 则默认的优先级为5 。 

  • 原创粉丝点击