这篇博客记录线程间通信相关 API 使用以及理解.
首先第一点, 我之前的博客里的线程之间也是通信的, 但是他们的通信是建立在访问的是同一个变量上的, 相当于是变量, 数据层面上的通信, 而下面要讲的是线程层面上的通信, 这种比前者更加可控.
Wait 和 notify 机制
首先明白为什么会出现这个机制.
目的: 举个例子, 现在有 A,B 两个线程, A 线程可以不停的改变 i 的值, B 线程再 i 的值为 5 时终止.
方法: 为了实现这种效果, 我们需要在 B 线程的 run 方法之中添加 while 循环, 不停的进行检测 i 值是否为 5, 为 5 则抛出异常停止或者使用 stop,interrupt 等.
问题: 检测 i 的值是否为 5 这个操作, 我们称之为轮询, 这里的肯定是很耗时很少的, 那么就会执行很多次, 但是其中有一些检测是没有必要的, 浪费了 CPU 的资源.
于是乎, 就产生了等待 / 唤醒机制, 为了解决 CPU 资源的浪费, 以及让程序更加可控.
先从字面上简单理解一下: 当一个线程执行某个操作但是不满足条件时, 先让它等候着, 直到条件满足了, 再将它唤醒. 当然唤醒就是说接着执行相应的操作.
wait 方法:
让当前线程进行等待, 将其加入到预执行队列当中, 直到终止或者被唤醒为止, 这里的预执行队列就是指处于和其他线程一起竞争获得该锁的状态. 并且 wait 会释放当前的锁, 这也就是说没有锁你是不能调用该方法的.
notify:
将处于 wait 状态, 且竞争的锁和调用 notify 方法的线程持有的锁相同的线程唤醒, 这个唤醒是随机的, 相当于在预执行队列当中随机唤醒一个线程. 不过注意 notify 唤醒并不会立即唤醒, 而是将当前同步代码块之中的代码执行结束之后再去唤醒, 相当于不会释放锁.
notifyAll: 顾名思义, 唤醒依赖于当前锁所有处于 wait 的线程.
下面通过一个简单的例子来验证上述结论, 就是之前的那个例子, A 线程列表元素不为 5 时 wait,B 线程负责为 5 时 notify:
MyList.java:
package 第三章_wait_join;
- import java.util.ArrayList;
- import java.util.List;
- public class MyList {
- private static List<String> list = new ArrayList<String>();
- public static void add(){
- list.add("##");
- }
- public static int getSize(){
- return list.size();
- }
- }
ThreadA.java
package 第三章_wait_join;
- public class ThreadA extends Thread{
- private String lock;
- public ThreadA(String lock){
- this.lock=lock;
- }
- @Override
- public void run(){
- try{
- synchronized(lock){
- if(MyList.getSize()!=5){ // 不为 5 则 wait
- System.out.println("等待开始...");
- lock.wait();
- System.out.println("等待结束...");
- }
- }
- }catch (InterruptedException e){
- e.printStackTrace();
- }
- }
- }
ThreadB.java
package 第三章_wait_join;
- public class ThreadB extends Thread{
- private String lock;
- public ThreadB(String lock){
- this.lock=lock;
- }
- @Override
- public void run(){
- try{
- synchronized(lock){
- for(int i=0;i<10;i++){
- MyList.add();
- if(MyList.getSize()==5){
- System.out.println("发出通知");
- lock.notify();
- }
- System.out.println("添加了"+(i+1)+"个元素");
- Thread.sleep(10);
- }
- }
- }catch (InterruptedException e){
- e.printStackTrace();
- }
- }
- }
- test.java:
package 第三章_wait_join;
- public class test {
- public static void main(String[] args){
- try {
- ThreadA A = new ThreadA("lock");
- ThreadB B = new ThreadB("lock");
- A.start();
- Thread.sleep(50);
- B.start();
- }catch (InterruptedException e){
- e.printStackTrace();
- }
- }
- }
运行结果:
可以看出来, A 线程开始等待之后就释放 lock 锁, B 线程获取到了该锁, 执行代码, 添加了 5 个元素时, 发出了通知, 但是发出通知之后, 它没有释放锁, 而是将同步代码块执行完, 然后再释放锁, A 线程获取到, 执行 wait 下面的代码.
说明两点:
1. 另外和之前一样, 如果一个线程已经处于阻塞状态了, 那么就不能再调用其他会产生阻塞的方法, 比如调用了 wait 就不能调用 interrupt,suspend, 否则会产生异常, 你无法阻塞一个已经被阻塞的线程.
2. 前面的 wait 都是没有参数的, wait(long) 就是说在 long 长时间之内, 如果没有被唤醒, 那么就自动唤醒该线程, 很好理解,
通知过早
那么 wait,notify 肯定也是有一定顺序的, 你不能还没有 wait 就 notify, 那么是不会 notify 任何线程的, 这也叫做通知过早. 看下面的例子:
更改之前的 test.java
package 第三章_wait_join;
- public class test {
- public static void main(String[] args){
- try {
- ThreadA A = new ThreadA("lock");
- ThreadB B = new ThreadB("lock");
- B.start();
- Thread.sleep(1000);
- A.start();
- }catch (InterruptedException e){
- e.printStackTrace();
- }
- }
- }
运行结果:
可以看到虽然发出了通知, 但是这个等待永远不会结束, 因为你在发出通知的时候线程还没有处于阻塞状态, 而是处于就绪状态, notify 并不会唤醒任何线程.
来源: https://www.cnblogs.com/eenio/p/11384099.html