【并发编程学习篇】深入理解Java线程

一、操作系统层面线程生命周期

操作系统层面的线程生命周期基本上可以用下图这个“五态模型”来描述。这五态分别是:初始状态可运行状态运行状态休眠状态终止状态

【并发编程学习篇】深入理解Java线程_第1张图片

  1. 初始状态,指的是线程已经被创建,但是还不允许分配 CPU 执行。这个状态属于编程语言特有的,不过这里所谓的被创建,仅仅是在编程语言层面被创建,而在操作系统层面,真正的线程还没有创建。

  2. 可运行状态,指的是线程可以分配 CPU 执行。在这种状态下,真正的操作系统线程已经被成功创建了,所以可以分配 CPU 执行。

  3. 当有空闲的 CPU 时,操作系统会将其分配给一个处于可运行状态的线程,被分配到 CPU 的线程的状态就转换成了运行状态。

  4. 运行状态的线程如果调用一个阻塞的 API(例如以阻塞方式读文件)或者等待某个事件(例如条件变量),那么线程的状态就会转换到休眠状态同时释放 CPU 使用权,休眠状态的线程永远没有机会获得 CPU 使用权。当等待的事件出现了,线程就会从休眠状态转换到可运行状态。

  5. 线程执行完或者出现异常就会进入终止状态,终止状态的线程不会切换到其他任何状态,进入终止状态也就意味着线程的生命周期结束了。

这五种状态在不同编程语言里会有简化合并。例如,C 语言的 POSIX Threads 规范,就把初始状态和可运行状态合并了;Java 语言里则把可运行状态和运行状态合并了,这两个状态在操作系统调度层面有用,而 JVM 层面不关心这两个状态,因为 JVM 把线程调度交给操作系统处理了。

二、Java线程的生命周期

Java 语言中线程共有六种状态,分别是:

  1. NEW(初始化状态)
  2. RUNNABLE(可运行状态+运行状态)
  3. BLOCKED(阻塞状态)
  4. WAITING(无时限等待)
  5. TIMED_WAITING(有时限等待)
  6. TERMINATED(终止状态)

在操作系统层面,Java 线程中的 BLOCKED、WAITING、TIMED_WAITING 是一种状态,即前面我们提到的休眠状态。也就是说只要 Java 线程处于这三种状态之一,那么这个线程就永远没有 CPU 的使用权。

【并发编程学习篇】深入理解Java线程_第2张图片

  1. 调用new方法新建一个线程,这时线程处于新建状态。
  2. 调用start方法启动一个线程,这时线程处于就绪状态。可运行状态下又分为就绪(Ready)状态和运行中(Running)状态两种状态。
  3. 就绪状态的线程获取到CPU时间片以后,线程就会调用run方法,此时线程从就绪状态切换为运行中状态。
  4. 运行中状态的线程调用yield方法或失去CPU时间片时,会再次切换到就绪状态

三、Thread常用方法

3.1 sleep方法

  1. 调用 sleep 方法会 释放CPU资源,当前线程从 Running 进入TIMED_WAITING 状态,不会释放对象锁
  2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException,并且会清除中断标志
  3. 睡眠结束后的线程未必会立刻得到执行
  4. sleep当传入参数为0时,和yield相同

3.2 yield方法

  1. yield方法会 释放CPU资源,让当前线程从 Running 进入 Ready 状态,让优先级更高(至少是相同)的线程获得执行机会,不会释放对象锁
  2. 假设当前进程只有main线程,当调用yield之后,main线程会继续运行,因为没有比它优先级更高的线程;
    具体的实现依赖于操作系统的任务调度器

3.3 join方法

  1. 等待调用join方法的线程结束之后,程序再继续执行,一般用于等待异步线程执行完结果之后才能继续运行的场景。
  2. join可以理解成是线程合并,当在一个线程调用另一个线程的join方法时,当前线程阻塞等待被调用join方法的线程执行完毕才能继续执行,所以join的好处能够保证线程的执行顺序,最后join的实现其实是基于 等待通知机制 的。
public class ThreadJoinDemo {

    public static void main(String[] sure) throws InterruptedException {

        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("t begin");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t finished");
            }
        });
        long start = System.currentTimeMillis();
        t.start();
        //主线程等待线程t执行完成
        t.join();

        System.out.println("执行时间:" + (System.currentTimeMillis() - start));
        System.out.println("Main finished");
    }

思考:如何正确优雅的停止线程? (下文见分晓)

3.5 stop方法

  1. stop()方法已经被jdk废弃,原因就是stop()方法强制的中断线程的执行,并且会 释放锁
  2. 线程执行到一半就被强制中断,存在线程安全问题,可能导致最终数据不一致
public class ThreadStopDemo {

    private static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {

        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + "获取锁");
                    try {
                        Thread.sleep(60000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(Thread.currentThread().getName() + "执行完成");
            }
        });
        thread.start();
        Thread.sleep(2000);
        // 停止thread,并释放锁
        thread.stop();

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "等待获取锁");
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + "获取锁");
                }
            }
        }).start();

    }

3.6 Object.wait方法

  1. 调用wait方法,会释放锁资源,且调用wait方法的 线程会进入 Waiting 状态
  2. 必须在同步方法块中配合Object.notify方法配合使用
public static void main(String[] args) {

        Object lock = new Object();

        Thread thread1 = new Thread(() -> {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + "    开始执行");
                    try {
                        lock.wait();
                        System.out.println("休息好了");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
            }

        }, "线程A");

        Thread thread2 = new Thread(() -> {
                synchronized (lock) {
                    System.out.println(Thread.currentThread().getName() + "   开始执行");
                    lock.notify();
                }
        }, "线程B");

        thread1.start();
        thread2.start();
}

四、Java线程的中断机制

  1. Java没有提供一种安全、直接的方法来停止某个线程,而是提供了中断机制。
  2. 中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理。
  3. 被中断的线程拥有完全的自主权,它既可以选择立即停止,也可以选择一段时间后停止,也可以选择压根不停止。

API的使用

interrupt(): 将线程的中断标志位设置为true,不会停止线程

isInterrupted(): 判断当前线程的中断标志位是否为true,不会清除中断标志位

Thread.interrupted():判断当前线程的中断标志位是否为true,并清除中断标志位,重置为fasle

public class ThreadInterruptTest {

    static int i = 0;

    public static void main(String[] args)  {
        System.out.println("begin");
        Thread t1 = new Thread(new Runnable() {
            @Override
            public  void run() {
                while (true) {
                    i++;
                    System.out.println(i);
                    //Thread.interrupted()  清除中断标志位
                    //Thread.currentThread().isInterrupted() 不会清除中断标志位
                    if (Thread.currentThread().isInterrupted() ) {
                        System.out.println("=========");
                    }
                    if(i==10){
                        break;
                    }

                }
            }
        });

        t1.start();
        //不会停止线程t1,只会设置一个中断标志位 flag=true
        t1.interrupt();
    }

利用中断机制优雅的停止线程

public class StopThread implements Runnable {

    @Override
    public void run() {
        int count = 0;
        while (!Thread.currentThread().isInterrupted() && count < 1000) {
            System.out.println("count = " + count++);
        }
        System.out.println("线程停止: stop thread");
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThread());
        thread.start();
        Thread.sleep(5);
        thread.interrupt();
    }

注意:使用中断机制时一定要注意是否存在中断标志位被清除的情况

sleep 期间能否感受到中断

修改上面的代码,线程执行任务期间有休眠需求

@Override
public void run() {
    int count = 0;
    while (!Thread.currentThread().isInterrupted() && count < 1000) {
        System.out.println("count = " + count++);

        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    System.out.println("线程停止: stop thread");
}

【并发编程学习篇】深入理解Java线程_第3张图片

  1. 处于休眠中的线程被中断,线程是可以感受到中断信号的,并且会抛出一个 InterruptedException 异常,同时清除中断信号,将中断标记位设置成 false。
  2. 这样就会导致while条件Thread.currentThread().isInterrupted()为false,程序会在不满足count < 1000这个条件时退出。
  3. 如果不在catch中重新手动添加中断信号,不做任何处理,就会屏蔽中断请求,有可能导致线程无法正确停止。
try {
    Thread.sleep(1);
} catch (InterruptedException e) {
    e.printStackTrace();
    //重新设置线程中断状态为true
    Thread.currentThread().interrupt();

结论:

  1. sleep可以被中断 抛出中断异常:sleep interrupted, 清除中断标志位
  2. wait可以被中断 抛出中断异常:InterruptedException, 清除中断标志位

你可能感兴趣的:(并发编程,java,学习,jvm)