2 Java并发原理精讲课程学习笔记

1.1、初始线程8大核心基础

  1. 实现多线程的方法到底有1种还是2种还是4种?
  2. 怎样才是正确的线程启动方式?
  3. 如何正确停止线程?
  4. 线程的一生——6个状态(生命周期)
  5. Thread和Object类中的重要方法详解
  6. 线程的各个属性
  7. 未捕获异常如何处理?
  8. 双刃剑:多线程会导致的问题

2.1、核心1:实现多线程的正确姿势

2.1.1、创建新线程

问题:实现多线程的方法是1种还是2种还是4种?

创建线程2种方式:
1.实现Runanble接口(推荐)->作为参数传入
代码演示:
/**
 * 描述:     用Runnable方式创建线程
 */
public class RunnableStyle implements Runnable{

    public static void main(String[] args) {
        Thread thread = new Thread(new RunnableStyle());
        thread.start();
    }

    @Override
    public void run() {
        System.out.println("用Runnable方法实现线程");
    }
}
执行结果:
用Runnable方法实现线程
2.继承Thread类
代码演示:
/**
 * 描述:     用Thread方式实现线程
 */
public class ThreadStyle extends Thread{

    @Override
    public void run() {
        System.out.println("用Thread类实现线程");
    }

    public static void main(String[] args) {
        new ThreadStyle().start();
    }
}

执行结果:

用Thread类实现线程

两种方法对比

方法1(实现Runnable接口)更好

实现Runnable接口好在哪里?

继承Thread类是不推荐的,因为它有以下的一些缺点:

  1. 从代码架构角度:具体的任务(run方法)应该和“创建和运行线程的机制(Thread类)”解耦,用runnable对象可以实现解耦。
  2. 使用继承Thread的方式的话,那么每次想新建一个任务,只能新建一个独立的线程,而这样做的损耗会比较大(比如重头开始创建一个线程、执行完毕以后再销毁等。如果线程的实际工作内容,也就是run()函数里只是简单的打印一行文字的话,那么可能线程的实际工作内容还不如损耗来的大)。如果使用Runnable和线程池,就可以大大减小这样的损耗。
  3. 继承Thread类以后,由于Java语言不支持双继承,这样就无法再继承其他的类,限制了可扩展性。
    通常我们优先选择方法1。

两种方法的本质对比

方法一和方法二,也就是“实现Runnable接口并传入Thread类”和“继承Thread类然后重写run()”在实现多线程的本质上,并没有区别,都是最终调用了start()方法来新建线程。这两个方法的最主要区别在于run()方法的内容来源:

@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

方法一:最终调用target.run();
方法二:run()整个都被重写

面试问题:有多少种实现线程的方法

题目描述:有多少种实现线程的方法?典型错误答案和正确答案。(问题选自《常见面试题汇总》)
提示:答题思路,以下5点:
1. 从不同的角度看,会有不同的答案。
2. 典型答案是两种,分别是实现Runnable接口和继承Thread类,然后具体展开说;
3. 但是,我们看原理,其实Thread类实现了Runnable接口,并且看Thread类的run方法,会发现其实那两种本质都是一样的,run方法的代码如下:

@Override
public void run() {
if (target != null) {
target.run();
}
}

方法一和方法二,也就是“继承Thread类然后重写run()”和“实现Runnable接口并传入Thread类”在实现多线程的本质上,并没有区别,都是最终调用了start()方法来新建线程。这两个方法的最主要区别在于run()方法的内容来源:
方法一:最终调用target.run();
方法二:run()整个都被重写

4. 然后具体展开说其他方式:还有其他的实现线程的方法,例如线程池、定时器,它们也能新建线程,但是细看源码,从没有逃出过本质,也就是实现Runnable接口和继承Thread类。
5. 结论:我们只能通过新建Thread类这一种方式来创建线程,但是类里面的run方法有两种方式来实现,第一种是重写run方法,第二种实现Runnable接口的run方法,然后再把该runnable实例传给Thread类。除此之外,从表面上看线程池、定时器等工具类也可以创建线程,但是它们的本质都逃不出刚才所说的范围。

以上这种描述比直接回答一种、两种、多种都更准确。

2.1.2、同时使用两种方法创建线程会怎样?

代码演示:

/**
 * 描述:     同时使用Runnable和Thread两种实现线程的方式。思路来自于网上的相同思路的面试题。
 */
public class BothRunnableThread {

    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("我来自Runnable");
            }
        }) {
            @Override
            public void run() {
                System.out.println("我来自Thread");
            }
        }.start();
    }
}

运行结果:

我来自Thread

2.2、核心2:怎样才是正确的线程启动方式?

2.2.1、启动线程的正确和错误方式

  1. start()和run()的比较
  2. start()方法原理解读
  3. run()方法原理解读

start()和run()的比较

代码演示:

/**
 * 描述:     对比start和run两种启动线程的方式
 */
public class StartAndRunMethod {

    public static void main(String[] args) {
        Runnable runnable = () -> {
            System.out.println(Thread.currentThread().getName());

        };
        runnable.run();// main

        new Thread(runnable).start(); // Thread-0
    }
}

运行结果:

main
Thread-0
主线程调用子线程start()方法:
主线程会通知JVM在空闲的时间启动子线程,但启动子线程的时机是由JVM线程调度器来决定的,所以说即使调用了start()方法,也不一定会立即启动子线程,所以调用start线程的顺序,并不能决定线程执行的顺序
子线程的创建准备工作:
1、处于就绪状态(获取到了除了CPU以外的所有资源,如:上下文、栈、线程状态)
做完准备工作,线程才能够被JVM调度到执行状态,等待获取CPU资源,然后才能够真正到运行状态
不能两次调用start方法,否则会报错。

演示:

/**
 * 描述:     演示不能两次调用start方法,否则会报错
 */
public class CantStartTwice {
    public static void main(String[] args) {
        Thread thread = new Thread();
        thread.start();
        thread.start();
    }

报错信息:

Exception in thread "main" java.lang.IllegalThreadStateException
	at java.base/java.lang.Thread.start(Thread.java:793)
	at com.lvxiaosha.threadcoreknowledge.startthread.CantStartTwice.main(CantStartTwice.java:10)

报错原因:线程开始执行,从new状态到后续的状态,线程执行完毕,就会变为终止状态,而终止状态永远不可能会返回回去,所以才会抛出异常。

start方法的执行流程是什么?

  1. 检查线程状态,只有NEW状态下的线程才能继续,否则会抛出IllegalThreadStateException(在运行中或者已结束的线程,都不能再次启动,详见CantStartTwice10类)
  2. 被加入线程组
  3. 调用start0()方法启动线程

注意点:
start方法是被synchronized修饰的方法,可以保证线程安全;
由JVM创建的main方法线程和system组线程,并不会通过start来启动。

run方法经典的3行代码
源码分析
public void run() {
if (target != null) {
target.run();
}
}
两种情况 :
target = null 时候;
直接跳过不执行任何操作;
!null 时候
执行重写run方法(看第二章巩固下)

2 Java并发原理精讲课程学习笔记_第1张图片

如果要启动一个线程必须要调用thread的run方法去启动。

面试问题:

启动线程常见面试问题
1、一个线程两次调用start()方法会出现什么情况?为什么?
Java的线程是不允许启动两次的,启动第二次会出现IllegalThreadStateException异常,这是一种运行时异常,多次调用start被认为是编程错误。因为在Java5以后,线程的状态会被明确的写入其公共内部枚举类型Java.lang.Thread.State中,分别是:新建,就绪,阻塞,等待、计时等待,终止。在第二次调用start() 方法的时候,已经被start的线程已经不再是(NEW)状态了,所以无论如何是不能再次启动的。
2、既然start()方法会调用run()方法,为什么我们选择调用start()方法,而不是直接掉用run()方法?
调用start方法是真正意义上启动了一个线程,会经历线程的各个生命周期。如果直接调用run()方法,就是一个普通的方法而已,不是使用子线程去调用。

2.3、核心3:如何正确停止线程?

2.3.1、停止线程

原理介绍:使用interrupt来通知,而不是强制停止线程。

Java中停止线程的原则是什么?

        在Java中,最好的停止线程的方式是使用中断interrupt,但是这仅仅是会通知到被终止的线程“你该停止运行了”,被终止的线程自身拥有决定权(决定是否、以及何时停止),这依赖于请求停止方和被停止方都遵守一种约定好的编码规范。

        任务和线程的启动很容易。在大多数时候,我们都会让它们运行直到结束,或者让它们自行停止。然而,有时候我们希望提前结束任务或线程,或许是因为用户取消了操作,或者服务需要被快速关闭,或者是运行超时或出错了。
        要使任务和线程能安全、快速、可靠地停止下来,并不是一件容易的事。Java没有提供任何机制来安全地终止线程。但它提供了中断( Interruption),这是一种协作机制,能够使一个线程终止另一个线程的当前工作。
        这种协作式的方法是必要的,我们很少希望某个任务、线程或服务立即停止,因为这种立即停止会使共享的数据结构处于不一致的状态。相反,在编写任务和服务时可以使用一种协作的方式:当需要停止时,它们首先会清除当前正在执行的工作,然后再结束。这提供了更好的灵活性,因为任务本身的代码比发出取消请求的代码更清楚如何执行清除工作。
        生命周期结束(End-of-Lifecycle)的问题会使任务、服务以及程序的设计和实现等过程变得复杂,而这个在程序设计中非常重要的要素却经常被忽略。一个在行为良好的软件与勉强运的软件之间的最主要区别就是,行为良好的软件能很完善地处理失败、关闭和取消等过程。

线程停止的情况
1、run方法的代码执行完毕
2、出现异常,并且方法中没有捕获

普通情况下停止线程的代码演示:

/**
 * 描述:     run方法内没有sleep或wait方法时,停止线程
 */
public class RightWayStopThreadWithoutSleep implements Runnable {

    @Override
    public void run() {
        int num = 0;
        while (!Thread.currentThread().isInterrupted() && num <= Integer.MAX_VALUE / 2) {
            if (num % 10000 == 0) {
                System.out.println(num + "是10000的倍数");
            }
            num++;
        }
        System.out.println("任务运行结束了");
    }

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

阻塞情况下停止线程代码演示:

/**
 * 描述:     带有sleep的中断线程的写法
 */
public class RightWayStopThreadWithSleep {

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;
            try {
                while (num <= 300 && !Thread.currentThread().isInterrupted()) {
                    if (num % 100 == 0) {
                        System.out.println(num + "是100的倍数");
                    }
                    num++;
                }
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(500);
        thread.interrupt();
    }
}

运行结果:

0是100的倍数
100是100的倍数
200是100的倍数
300是100的倍数
java.lang.InterruptedException: sleep interrupted
	at java.base/java.lang.Thread.sleep(Native Method)
	at com.lvxiaosha.threadcoreknowledge.stopthreads.RightWayStopThreadWithSleep.lambda$main$0(RightWayStopThreadWithSleep.java:18)
	at java.base/java.lang.Thread.run(Thread.java:833)
Thread.sleep时可以感应到别的线程提出的interrupt请求,抛出InterruptedException。

线程在每次迭代后都阻塞的情况停止线程代码演示:

/**
 * 描述:     如果在执行过程中,每次循环都会调用sleep或wait等方法,那么不需要每次迭代都检查是否已中断
 */
public class RightWayStopThreadWithSleepEveryLoop {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;
            try {
                while (num <= 10000) {
                    if (num % 100 == 0) {
                        System.out.println(num + "是100的倍数");
                    }
                    num++;
                    Thread.sleep(10);
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
}

运行结果:

0是100的倍数
100是100的倍数
200是100的倍数
300是100的倍数
400是100的倍数
java.lang.InterruptedException: sleep interrupted
	at java.base/java.lang.Thread.sleep(Native Method)
	at com.lvxiaosha.threadcoreknowledge.stopthreads.RightWayStopThreadWithSleepEveryLoop.lambda$main$0(RightWayStopThreadWithSleepEveryLoop.java:16)
	at java.base/java.lang.Thread.run(Thread.java:833)
每次迭代都会阻塞:
    这种情况,线程耗时主要集中在sleep或其他阻塞方法上,其他的操作执行的是很快的。当我们发出一个中断信号后,能够响应这个信号的代码应该就是这个sleep方法,又因为sleep方法可以正确的响应中断,所以在每次循环时就没有必要再检查当前线程是否已经被中断(Thread.currentThreah.isInterrupted())。

        在实际业务中,为了保险起见,最好还是while多判断一次。

如果while里面放try/catch,会导致中断失效。

代码演示:

/**
 * 描述:     如果while里面放try/catch,会导致中断失效
 */
public class CantInterrupt {

    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = () -> {
            int num = 0;
            while (num <= 10000 && !Thread.currentThread().isInterrupted()) {
                if (num % 100 == 0) {
                    System.out.println(num + "是100的倍数");
                }
                num++;
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        Thread thread = new Thread(runnable);
        thread.start();
        Thread.sleep(5000);
        thread.interrupt();
    }
}

运行结果:

0是100的倍数
100是100的倍数
200是100的倍数
300是100的倍数
400是100的倍数
java.lang.InterruptedException: sleep interrupted
	at java.base/java.lang.Thread.sleep(Native Method)
	at com.lvxiaosha.threadcoreknowledge.stopthreads.CantInterrupt.lambda$main$0(CantInterrupt.java:17)
	at java.base/java.lang.Thread.run(Thread.java:833)
500是100的倍数
600是100的倍数
700是100的倍数
800是100的倍数

        可以看到程序没有终止。Java语言在设计sleep方法时,有这样的理念:一旦响应中断,会将线程的interrupt标记位清除。

中断线程的两种最佳实践:
1.传递中断(被线程调用的方法上抛出异常)
2.恢复中断(在被线程调用的方法中可以try/catch异常,但是一定要继续发出相应线程的中断信号,也就是thread.interrupt)

传递中断代码演示:

/**
 * 描述:     最佳实践:catch了InterruptedExcetion之后的优先选择:在方法签名中抛出异常 那么在run()就会强制try/catch
 * RightWayStopThread相关的类思路代码参考自Java Concurrency In Practice的线程停止相关小节
 */
public class RightWayStopThreadInProd implements Runnable {

    @Override
    public void run() {
        while (true && !Thread.currentThread().isInterrupted()) {
            System.out.println("go");
            try {
                throwInMethod();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
                //保存日志、停止程序
                System.out.println("保存日志");
                e.printStackTrace();
            }
        }
    }

    private void throwInMethod() throws InterruptedException {
            Thread.sleep(2000);
    }

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

运行结果:

go
保存日志
java.lang.InterruptedException: sleep interrupted
	at java.base/java.lang.Thread.sleep(Native Method)
	at com.lvxiaosha.threadcoreknowledge.stopthreads.RightWayStopThreadInProd.throwInMethod(RightWayStopThreadInProd.java:25)
	at com.lvxiaosha.threadcoreknowledge.stopthreads.RightWayStopThreadInProd.run(RightWayStopThreadInProd.java:14)
	at java.base/java.lang.Thread.run(Thread.java:833)

处理中断的最好方法是什么?

        优先选择在方法上抛出异常。
        用throws InterruptedException 标记你的方法,不采用try 语句块捕获异常,以便于该异常可以传递到顶层,让run方法可以捕获这一异常,例如:

void subTask() throws InterruptedException
	sleep(delay);
}

        由于run方法内无法抛出checked Exception(只能用try catch),顶层方法必须处理该异常,避免了漏掉或者被吞掉的情况,增强了代码的健壮性。

恢复中断代码演示:

/**
 * 描述:最佳实践2:在catch子语句中调用Thread.currentThread().interrupt()
来恢复设置中断状态,以便于在后续的执行中,依然能够检查到刚才发生了中断
 * 回到刚才RightWayStopThreadInProd补上中断,让它跳出
 */
public class RightWayStopThreadInProd2 implements Runnable {

    @Override
    public void run() {
        while (true) {
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("Interrupted,程序运行结束");
                break;
            }
            reInterrupt();
        }
    }

    private void reInterrupt() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            e.printStackTrace();
        }
    }

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

2 Java并发原理精讲课程学习笔记_第2张图片

 2 Java并发原理精讲课程学习笔记_第3张图片

Java异常体系

2 Java并发原理精讲课程学习笔记_第4张图片

error:程序没有办法处理
Exception:运行时异常(非受检异常)和非运行时异常(受检异常)
受检异常:需要处理,来提高程序的健壮性

错误的线程停止方法

1.stop()强制停止,停止线程会释放当前所拥有的锁,无法保证操作的原子性,可能造成脏数据
2.suspen,resume方法不会放弃当前拥有的锁,线程挂起,可能会一直无法被唤醒或者导致死锁

2 Java并发原理精讲课程学习笔记_第5张图片

interrupt状态

2 Java并发原理精讲课程学习笔记_第6张图片

static boolean interrupted() ----针对当前运行线程,会清除状态
public boolean isInterrupted()---针对调用线程,不会清除状态
Thread.interrupted() 目标对象是当前执行它的线程,而不是调用它的线程

练习:

/**
 * 描述:     注意Thread.interrupted()方法的目标对象是“当前线程”,而不管本方法来自于哪个对象。
 * 代码出自Java并发编程之美1.7线程中断,感谢作者翟陆续
 */
public class RightWayInterrupted {

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

        Thread threadOne = new Thread(new Runnable() {
            @Override
            public void run() {
                for (; ; ) {
                }
            }
        });

        // 启动线程
        threadOne.start();
        //设置中断标志
        threadOne.interrupt();
        //获取中断标志
        System.out.println("isInterrupted: " + threadOne.isInterrupted());
        //获取中断标志并重置
        System.out.println("isInterrupted: " + threadOne.interrupted());
        //获取中断标志并重直
        System.out.println("isInterrupted: " + Thread.interrupted());
        //获取中断标志
        System.out.println("isInterrupted: " + threadOne.isInterrupted());
        threadOne.join();
        System.out.println("Main thread is over.");
    }
}

输出结果如下:

isInterrupted: true
isInterrupted: false
isInterrupted: false
isInterrupted: true
isInterrupted: true --->上一行调用了interrupt,所以这里为true,不会清除这个线程的状态
isInterrupted: false ---> interrupted是静态方法,这里不管看起来是threadOne 这个对象,或者是Thread类来进行调用,其实都是返回主线程的中断标志
isInterrupted: false --->同上第二点
isInterrupted: true 后面没有对threadOne线程的状态进行改变,所以依然会true

常见面试题:

1、如何停止线程?

A. 用volatile的boolean作为标记来停止
B. 用stop()方法让线程停止
C. 用interrupt来请求线程停止

解答:
应该选C。

  1. 原理:用interrupt来请求线程停止而不是强制,好处是安全。
  2. 想停止线程,要请求方、被停止方、子方法被调用方相互配合才行:
    a) 作为被停止方:每次循环中或者适时检查中断信号,并且在可能抛出InterrupedException的地方处理该中断信号;
    b) 请求方:发出中断信号;
    c) 子方法调用方(被线程调用的方法的作者)要注意:优先在方法层面抛出InterrupedException,或者检查到中断信号时,再次设置中断状态;
  3. 最后再说错误的方法:stop/suspend已废弃,volatile的boolean无法处理长时间阻塞的情况

2、无法响应中断时如何停止线程?

A. 用interrupt方法来请求停止线程
B. 不可中断的阻塞无法处理
C. 根据不同的类调用不同的方法

解答:
应该选C。
如果线程阻塞是由于调用了 wait(),sleep() 或 join() 方法,你可以中断线程,通过抛出 InterruptedException 异常来唤醒该线程。
但是对于不能响应InterruptedException的阻塞,很遗憾,并没有一个通用的解决方案。
但是我们可以利用特定的其它的可以响应中断的方法,比如ReentrantLock.lockInterruptibly(),比如关闭套接字使线程立即返回等方法来达到目的。
答案有很多种,因为有很多原因会造成线程阻塞,所以针对不同情况,唤起的方法也不同。

总结就是说如果不支持响应中断,就要用特定方法来唤起,没有万能药。

 2.4、核心4:线程的一生——6个状态(生命周期)

2.4.1、线程状态

2 Java并发原理精讲课程学习笔记_第7张图片

线程的6种状态
1、New:已经创建,但还没有启动的新线程(没有执行start方法)。
2、Runnable:一旦调用start方法之后,处于runnable状态。
1)调用start方法之后,线程并不会启动,可能系统没有资源分配,还处于等待状态,那这种情况下时runnable还是waiting呢,其实还是runnbale状态 ,不会从new到waiting状态。
2)线程没有启动,为什么叫runnbale状态呢,runnbale是可运行的,而不是运行中。
3)Java中的runnable对应os中的ready和running。
4)runnbale既可以是可运行的,又可以是正在运行中的。
3、Blocked
1)线程进入被synchronize修饰的代码块之后,并且该锁被其他线程拿走,线程就是blocked状态
2)blocked仅仅是针对synchronized这个关键字
3)和synchronized关键字起到相似效果的还有其他锁,可重入锁、读写锁等都可以让线程进入等待的情况,但是那些情况下不是blocked的线程状态
4、Waiting:等待唤醒
5、Timed Waiting:计时等待(等待超时自动唤醒或者主动唤醒)
6、Terminated:终止状态
1)run方法执行完毕
2)出现异常
线程的NEW、RUNNABLE、Terminated状态代码演示:
/**
 * 描述:     展示线程的NEW、RUNNABLE、Terminated状态。即使是正在运行,也是Runnable状态,而不是Running。
 */
public class NewRunnableTerminated implements Runnable {

    public static void main(String[] args) {
        Thread thread = new Thread(new NewRunnableTerminated());
        //打印出NEW的状态
        System.out.println(thread.getState());
        thread.start();
        System.out.println(thread.getState());
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //打印出RUNNABLE的状态,即使是正在运行,也是RUNNABLE,而不是RUNNING
        System.out.println(thread.getState());
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //打印出TERMINATED状态
        System.out.println(thread.getState());
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(i);
        }
    }
}

执行结果:

NEW
RUNNABLE
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
RUNNABLE
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
TERMINATED

展示Blocked, Waiting, TimedWaiting线程状态代码演示:
/**
 * 描述:     展示Blocked, Waiting, TimedWaiting
 */
public class BlockedWaitingTimedWaiting implements Runnable{
    public static void main(String[] args) {
        BlockedWaitingTimedWaiting runnable = new BlockedWaitingTimedWaiting();
        Thread thread1 = new Thread(runnable);
        thread1.start();
        Thread thread2 = new Thread(runnable);
        thread2.start();
        try {
            Thread.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //打印出Timed_Waiting状态,因为正在执行Thread.sleep(1000);
        System.out.println(thread1.getState());
        //打印出BLOCKED状态,因为thread2想拿得到sync()的锁却拿不到
        System.out.println(thread2.getState());
        try {
            Thread.sleep(1300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //打印出WAITING状态,因为执行了wait()
        System.out.println(thread1.getState());

    }

    @Override
    public void run() {
        syn();
    }

    private synchronized void syn() {
        try {
            Thread.sleep(1000);
            wait();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

运行结果:

TIMED_WAITING
BLOCKED
WAITING

2.4.2、阻塞状态

2 Java并发原理精讲课程学习笔记_第8张图片

2.4.3、面试题

线程有哪几种状态?生命周期是什么?

2.5、核心5:Thread和Object类中的重要方法详解

主要方法

1、为什么线程通信的方法wait(),notify()和notifyAll()被定义在Object类里面?而sleep定义在了Thread类中?
2、用3中方式实现生产者模式
3、java se 8 和java 1.8 和jdk 8是什么关系,是同一个东西吗?
4、join和sleep和wait期间线程的状态分别是什么?为什么?

2.5.1、wait方法

2 Java并发原理精讲课程学习笔记_第9张图片

阻塞阶段
wait
一个线程或者多个线程暂时休息,等到后续需要时再唤醒。
执行wait方法时,必须先拥有对象的monitor锁,调用wait方法之后,调用这个方法的线程进入阻塞状态,不参与线程的调度,直到以下4种情况之一发生时,才会被唤醒。
1)另一个线程调用这个对象的notify()方法且刚好被唤醒的是本线程(notify只唤醒一个线程)
2)另外一个线程调用这个对象的notifyAll()方法。
3)过了wait(long timeout)规定的超时时间,如果传入为0就是永久等待。
4)线程自身调用了interrupt()。
唤醒阶段
notify:唤醒单个正在等待某对象monitor的线程,唤醒时,如果有多个线程都在等待,选择其中一个唤醒(具体的唤醒选择任意的,Java对此没有一个严格的规范,JVM可以拥有自己的实现)。
notify和wait都需要在synchronized关键字保护的方法或者代码块中执行,在外面执行的话,会抛出异常
notifyAll:唤醒所有等待的线程。
遇到中断
假设线程执行了wait方法,在此期间如果被中断,会抛出InterruptedException,释放掉已经获取到的monitor。

wait和notify的基本用法代码演示:

/**
 * 描述:     展示wait和notify的基本用法 
 * 1. 研究代码执行顺序 
 * 2. 证明wait释放锁
 */
public class Wait {

    public static Object object = new Object();

    static class Thread1 extends Thread {

        @Override
        public void run() {
            synchronized (object) {
                System.out.println(Thread.currentThread().getName() + "开始执行了");
                try {
                    object.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程" + Thread.currentThread().getName() + "获取到了锁。");
            }
        }
    }

    static class Thread2 extends Thread {

        @Override
        public void run() {
            synchronized (object) {
                object.notify();
                System.out.println("线程" + Thread.currentThread().getName() + "调用了notify()");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread1 thread1 = new Thread1();
        Thread2 thread2 = new Thread2();
        thread1.start();
        Thread.sleep(200);
        thread2.start();
    }
}

运行结果:

Thread-0开始执行了
线程Thread-1调用了notify()
线程Thread-0获取到了锁。

代码演示:3个线程,线程1和线程2首先被阻塞,线程3唤醒它们。notify, notifyAll。 start先执行不代表线程先启动。
/**
 * 描述:     3个线程,线程1和线程2首先被阻塞,线程3唤醒它们。
 * notify, notifyAll。
 * start先执行不代表线程先启动。
 */
public class WaitNotifyAll implements Runnable {

    private static final Object resourceA = new Object();


    public static void main(String[] args) throws InterruptedException {
        Runnable r = new WaitNotifyAll();
        Thread threadA = new Thread(r);
        Thread threadB = new Thread(r);
        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA) {
                    resourceA.notifyAll();
//                    resourceA.notify();
                    System.out.println("ThreadC notified.");
                }
            }
        });
        threadA.start();
        threadB.start();
        Thread.sleep(200);
        threadC.start();
    }
    @Override
    public void run() {
        synchronized (resourceA) {
            System.out.println(Thread.currentThread().getName()+" got resourceA lock.");
            try {
                System.out.println(Thread.currentThread().getName()+" waits to start.");
                resourceA.wait();
                System.out.println(Thread.currentThread().getName()+"'s waiting to end.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:

Thread-0 got resourceA lock.
Thread-0 waits to start.
Thread-1 got resourceA lock.
Thread-1 waits to start.
ThreadC notified.
Thread-0's waiting to end.
Thread-1's waiting to end.

/**
 * 描述:     3个线程,线程1和线程2首先被阻塞,线程3唤醒它们。
 * notify, notifyAll。
 * start先执行不代表线程先启动。
 */
public class WaitNotifyAll implements Runnable {

    private static final Object resourceA = new Object();


    public static void main(String[] args) throws InterruptedException {
        Runnable r = new WaitNotifyAll();
        Thread threadA = new Thread(r);
        Thread threadB = new Thread(r);
        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA) {
//                    resourceA.notifyAll();
                    resourceA.notify();
                    System.out.println("ThreadC notified.");
                }
            }
        });
        threadA.start();
        threadB.start();
        Thread.sleep(200);
        threadC.start();
    }
    @Override
    public void run() {
        synchronized (resourceA) {
            System.out.println(Thread.currentThread().getName()+" got resourceA lock.");
            try {
                System.out.println(Thread.currentThread().getName()+" waits to start.");
                resourceA.wait();
                System.out.println(Thread.currentThread().getName()+"'s waiting to end.");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:

Thread-0 got resourceA lock.
Thread-0 waits to start.
ThreadC notified.
Thread-1 got resourceA lock.
Thread-1 waits to start.
Thread-0's waiting to end.

wait只释放当前的那把锁,不同的锁之间相互独立。

代码演示:

/**
 * 描述:     证明wait只释放当前的那把锁
 */
public class WaitNotifyReleaseOwnMonitor {

    private static volatile Object resourceA = new Object();
    private static volatile Object resourceB = new Object();

    public static void main(String[] args) {
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (resourceA) {
                    System.out.println("ThreadA got resourceA lock.");
                    synchronized (resourceB) {
                        System.out.println("ThreadA got resourceB lock.");
                        try {
                            System.out.println("ThreadA releases resourceA lock.");
                            resourceA.wait();

                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });

        Thread thread2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (resourceA) {
                    System.out.println("ThreadB got resourceA lock.");
                    System.out.println("ThreadB tries to resourceB lock.");

                    synchronized (resourceB) {
                        System.out.println("ThreadB got resourceB lock.");
                    }
                }
            }
        });

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

运行结果:

ThreadA got resourceA lock.
ThreadA got resourceB lock.
ThreadA releases resourceA lock.
ThreadB got resourceA lock.
ThreadB tries to resourceB lock.

wait、notify、notifyAll特点、性质
1、必须先拥有monitor
2、notify只能唤醒一个 ,唤醒哪个线程取决于JVM的实现
3、属于Object类
4、类似功能的Condition
5、同时拥有多个锁的情况(只会释放现在wait所对应的对象的那把锁,如果对象持有多把锁,注意释放顺序,否则会发生死锁)

2 Java并发原理精讲课程学习笔记_第10张图片

 2 Java并发原理精讲课程学习笔记_第11张图片

 2 Java并发原理精讲课程学习笔记_第12张图片

 

重点:使用wait/notify实现生产者消费者设计模式

2 Java并发原理精讲课程学习笔记_第13张图片

 2 Java并发原理精讲课程学习笔记_第14张图片

 

代码演示:

import java.util.Date;
import java.util.LinkedList;

/**
 * 描述:     用wait/notify来实现生产者消费者模式
 * 思路参考自Java concurrency Cookbook Using conditions in synchronized code
 */
public class ProducerConsumerModel {
    public static void main(String[] args) {
        EventStorage eventStorage = new EventStorage();
        Producer producer = new Producer(eventStorage);
        Consumer consumer = new Consumer(eventStorage);
        new Thread(producer).start();
        new Thread(consumer).start();
    }
}

class Producer implements Runnable {

    private EventStorage storage;

    public Producer(
            EventStorage storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            storage.put();
        }
    }
}

class Consumer implements Runnable {

    private EventStorage storage;

    public Consumer(
            EventStorage storage) {
        this.storage = storage;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            storage.take();
        }
    }
}

class EventStorage {

    private int maxSize;
    private LinkedList storage;

    public EventStorage() {
        maxSize = 10;
        storage = new LinkedList<>();
    }

    public synchronized void put() {
        while (storage.size() == maxSize) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        storage.add(new Date());
        System.out.println("仓库里有了" + storage.size() + "个产品。");
        notify();
    }

    public synchronized void take() {
        while (storage.size() == 0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("拿到了" + storage.poll() + ",现在仓库还剩下" + storage.size());
        notify();
    }
}

你可能感兴趣的:(多线程,java并发)