7.并发编程--多线程通信-wait-notify

并发编程--多线程通信-wait-notify

多线程通信:线程通信的目的是为了能够让线程之间相互发送信号;

1. 多线程通信:

线程通信的目的是为了能够让线程之间相互发送信号。另外,线程通信还能够使得线程等待其它线程的信号,比如,线程B可以等待线程A的信号,这个信号可以是线程A已经处理完成的信号;
Object提供了三个方法wait(), notify(), notifyAll()在线程之间进行通信,以此来解决线程间执行顺序等问题。

  • * wait():释放当前线程的同步监视控制器,并让当前线程进入阻塞状态,直到别的线程发出notify将该线程唤醒。
  • * notify():唤醒在等待控制监视器的其中一个线程(随机)。只有当前线程释放了同步监视器锁(调用wait)之后,被唤醒的线程才有机会执行。
  • * notifyAll():与上面notify的区别是同时唤醒多个等待线程。

值得注意的是这三个方法是属于Object而不是属于Thread的,但是调用的时候必须用同步监视器来调用,wait(), notify(), notifyAll() 必须和synchronized关键字联合使用

模拟线程通信:自定义实现的通信模式
示例:ListAdd1.java

 1   public class ListAdd1 {
 2     private volatile static List list = new ArrayList();
 3 
 4       public void add(){
 5         list.add("bjsxt");
 6       }
 7       public int size(){
 8         return list.size();
 9       }
10 
11       public static void main(String[] args) {
12 
13         final ListAdd1 list1 = new ListAdd1();
14 
15         Thread t1 = new Thread(new Runnable() {
16           @Override
17           public void run() {
18             try {
19               for(int i = 0; i <10; i++){
20                 list1.add();
21                 System.out.println("当前线程:" + Thread.currentThread().getName() + "添加了一个元素..");
22                 Thread.sleep(500);
23               }
24             } catch (InterruptedException e) {
25               e.printStackTrace();
26             }
27           }
28         }, "t1");
29 
30         Thread t2 = new Thread(new Runnable() {
31           @Override
32           public void run() {
33             while(true){
34               if(list1.size() == 5){
35                 System.out.println("当前线程收到通知:" + Thread.currentThread().getName() + " list size = 5 线程停止..");
36                 throw new RuntimeException();
37               }
38             }
39           }
40         }, "t2");        
41 
42         t1.start();
43         t2.start();
44       }
45   }

2. 使用JDK的 Object提供了三个方法wait(), notify(), notifyAll()在线程之间进行通信

示例:

 1   import java.util.ArrayList;
 2   import java.util.List;
 3   import java.util.Queue;
 4   import java.util.concurrent.CountDownLatch;
 5   import java.util.concurrent.LinkedBlockingDeque;
 6   import java.util.concurrent.LinkedBlockingQueue;
 7   /**
 8   * wait notfiy 方法,wait释放锁,notfiy不释放锁
 9   * @@author Maozw
10   *
11   */
12   public class ListAdd2 {
13       private volatile static List list = new ArrayList();
14 
15       public void add(){
16         list.add("bjsxt");
17       }
18       public int size(){
19         return list.size();
20       }
21 
22       public static void main(String[] args) {
23 
24         final ListAdd2 list2 = new ListAdd2();
25 
26         // 1 实例化出来一个 lock
27         // 当使用wait 和 notify 的时候 , 一定要配合着synchronized关键字去使用
28         //final Object lock = new Object();
29 
30         final CountDownLatch countDownLatch = new CountDownLatch(1);
31 
32         Thread t1 = new Thread(new Runnable() {
33           @Override
34           public void run() {
35             try {
36               //synchronized (lock) {
37                 for(int i = 0; i <10; i++){
38                   list2.add();
39                   System.out.println("当前线程:" + Thread.currentThread().getName() + "添加了一个元素..");
40                   Thread.sleep(500);
41                   if(list2.size() == 5){
42                     System.out.println("已经发出通知..");
43                     countDownLatch.countDown();
44                     //lock.notify();
45                   }
46                 }                        
47               //}
48             } catch (InterruptedException e) {
49               e.printStackTrace();
50             }
51 
52           }
53         }, "t1");
54 
55         Thread t2 = new Thread(new Runnable() {
56           @Override
57           public void run() {
58             //synchronized (lock) {
59               if(list2.size() != 5){
60                 try {
61                   //System.out.println("t2进入...");
62                   //lock.wait();
63                   countDownLatch.await();
64                 } catch (InterruptedException e) {
65                   e.printStackTrace();
66                 }
67               }
68               System.out.println("当前线程:" + Thread.currentThread().getName() + "收到通知线程停止..");
69               throw new RuntimeException();
70             //}
71           }
72         }, "t2");
73 
74         t2.start();
75         t1.start();
76 
77       }
78   }

问题1:wait()方法外面为什么是while循环而不是if判断?

问题2:notify()是唤醒一个线程,notifyAll()是唤醒全部线程,但是唤醒然后呢,不管是notify()还是notifyAll(),最终拿到锁的只会有一个线程,那它们到底有什么区别呢?

OK! 要回答上述两个问题?我们首先需要明白java对象锁的模型:
JVM 会为每一个使用内部锁(synchronized)的对象维护两个集合,Entry Set和Wait Set,也有人翻译为锁池和等待池,意思基本一致。
**Entry Set**

  • 如果线程A已经持有了对象锁,此时如果有其他线程也想获得该对象锁的话,它只能进入Entry Set,并且处于线程的BLOCKED状态。

**Wait Set**

  • 如果线程A调用了wait()方法,那么线程A会释放该对象的锁,进入到Wait Set,并且处于线程的WAITING状态。

sequenceDiagram
Entry Set(锁池)->>Wait Set(等待池): wait()
Wait Set(等待池)->>Entry Set(锁池): noitify()

 注意:某个线程B想要获得对象锁,一般情况下有两个先决条件,

  • 一是对象锁已经被释放了(如曾经持有锁的前任线程A执行完了synchronized代码块或者调用了wait()方法等等)
  • 二是线程B已处于RUNNABLE状态。

那么这两类集合中的线程都是在什么条件下可以转变为RUNNABLE呢?

  • 对于Entry Set中的线程,当对象锁被释放的时候,JVM会唤醒处于Entry Set中的某一个线程,这个线程的状态就从BLOCKED转变为RUNNABLE。
  • 对于Wait Set中的线程,当对象的notify()方法被调用时,JVM会唤醒处于Wait Set中的某一个线程,这个线程的状态就从WAITING转变为RUNNABLE;或者当notifyAll()方法被调用时,Wait Set中的全部线程会转变为RUNNABLE状态。所有Wait Set中被唤醒的线程会被转移到Entry Set中,然后 每当对象的锁被释放后,那些所有处于RUNNABLE状态的线程会共同去竞争获取对象的锁.

解答
 第一个问题 :wait()方法外面为什么是while循环而不是if判断?

  •  因为wait()的线程永远不能确定其他线程会在什么状态下notify(),所以必须在被唤醒、抢占到锁并且从wait()方法退出的时候再次进行指定条件的判断,以决定是满足条件往下执行呢还是不满足条件再次wait()呢。

第二个问题:既然notify()和notifyAll()最终的结果都是只有一个线程能拿到锁,那唤醒一个和唤醒多个有什么区别呢?

  • 通过下面这个例子可以非常好的说明;是这样一个场景:两个生产者两个消费者的场景,我们都使用notify()而非notifyAll(),假设消费者线程1拿到了锁,判断buffer为空,那么wait(),释放锁;然后消费者2拿到了锁,同样buffer为空,wait(),也就是说此时Wait Set中有两个线程;然后生产者1拿到锁,生产,buffer满,notify()了, 那么可能消费者1被唤醒了,但是此时还有另一个线程生产者2在Entry Set中盼望着锁,并且最终抢占到了锁, 但因为此时buffer是满的,因此它要wait();然后消费者1拿到了锁,消费,notify();这时就有问题了,此时生产者2和消费者2都在Wait Set中,buffer为空,如果唤醒生产者2,没毛病;但如果唤醒了消费者2,因为buffer为空,它会再次wait(),这就尴尬了,万一生产者1已经退出不再生产了,没有其他线程在竞争锁了,只有生产者2和消费者2在Wait Set中互相等待,那传说中的死锁就发生了。
  • notify()换成notifyAll(),这样的情况就不会再出现了,因为每次notifyAll()都会使其他等待的线程从Wait Set进入Entry Set,从而有机会获得锁。
 1 import java.util.ArrayList;
 2 import java.util.List;
 3 
 4 public class Something {
 5     private Buffer mBuf = new Buffer();
 6 
 7     public void produce() {
 8         synchronized (this) {
 9             while (mBuf.isFull()) {
10                 try {
11                     wait();
12                 } catch (InterruptedException e) {
13                     e.printStackTrace();
14                 }
15             }
16             mBuf.add();
17             notifyAll();
18         }
19     }
20 
21     public void consume() {
22         synchronized (this) {
23             while (mBuf.isEmpty()) {
24                 try {
25                     wait();
26                 } catch (InterruptedException e) {
27                     e.printStackTrace();
28                 }
29             }
30             mBuf.remove();
31             notifyAll();
32         }
33     }
34 
35     private class Buffer {
36         private static final int MAX_CAPACITY = 1;
37         private List innerList = new ArrayList<>(MAX_CAPACITY);
38 
39         void add() {
40             if (isFull()) {
41                 throw new IndexOutOfBoundsException();
42             } else {
43                 innerList.add(new Object());
44             }
45             System.out.println(Thread.currentThread().toString() + " add");
46 
47         }
48 
49         void remove() {
50             if (isEmpty()) {
51                 throw new IndexOutOfBoundsException();
52             } else {
53                 innerList.remove(MAX_CAPACITY - 1);
54             }
55             System.out.println(Thread.currentThread().toString() + " remove");
56         }
57 
58         boolean isEmpty() {
59             return innerList.isEmpty();
60         }
61 
62         boolean isFull() {
63             return innerList.size() == MAX_CAPACITY;
64         }
65     }
66 
67     public static void main(String[] args) {
68         Something sth = new Something();
69         Runnable runProduce = new Runnable() {
70             int count = 4;
71 
72             @Override
73             public void run() {
74                 while (count-- > 0) {
75                     sth.produce();
76                 }
77             }
78         };
79         Runnable runConsume = new Runnable() {
80             int count = 4;
81 
82             @Override
83             public void run() {
84                 while (count-- > 0) {
85                     sth.consume();
86                 }
87             }
88         };
89         for (int i = 0; i < 2; i++) {
90             new Thread(runConsume).start();
91         }
92         for (int i = 0; i < 2; i++) {
93             new Thread(runProduce).start();
94         }
95     }
96 }

join

首先,join()是Thread类的一个方法,而不是object的方法;
JDK中是这样描述的:

//join()方法的作用,是等待这个线程结束
public final void join()throws InterruptedException: Waits for this thread to die.
在Java 7 Concurrency Cookbook"的定义为:
join() method suspends the execution of the calling thread until the object called finishes its execution.
也就是说,t.join()方法阻塞调用此方法的线程(calling thread),直到线程t完成,此线程再继续;
举个例子:通常用于在main()主线程内,等待其它线程完成再结束main()主线程。例如:
 1 package com.maozw.springmvc.controller;
 2 
 3 import java.util.Date;
 4 import java.util.concurrent.TimeUnit;
 5 
 6 public class JoinTest implements Runnable {
 7 
 8     private String name;
 9 
10     public JoinTest(String name) {
11         this.name = name;
12     }
13 
14     public void run() {
15         System.out.printf("%s begins: %s\n", name, new Date());
16         try {
17             TimeUnit.SECONDS.sleep(4);
18         } catch (InterruptedException e) {
19             e.printStackTrace();
20         }
21         System.out.printf("%s has finished: %s\n", name, new Date());
22     }
23 
24     public static void main(String[] args) {
25         Thread thread1 = new Thread(new JoinTest("One"));
26         Thread thread2 = new Thread(new JoinTest("Two"));
27         thread1.start();
28         thread2.start();
29 
30         try {
31             thread1.join();
32             thread2.join();
33         } catch (InterruptedException e) {
34             e.printStackTrace();
35         }
36         System.out.println("Main thread is finished");
37     }
38 }

输出结果

1 One begins: Mon Jul 23 22:41:21 CST 2018
2 Two begins: Mon Jul 23 22:41:21 CST 2018
3 Two has finished: Mon Jul 23 22:41:25 CST 2018
4 One has finished: Mon Jul 23 22:41:25 CST 2018
5 Main thread is finished

解说join原理:

我们尝试去打开的起源码:

  • 通过源码可以看出,Join方法实现是通过wait。 当main线程调用t.join时候,main线程会获得线程对象t的锁(wait 意味着拿到该对象的锁),调用该对象的wait(等待时间),直到该对象唤醒main线程 ,比如退出后。这就意味着main 线程调用t.join时,必须能够拿到线程t对象的锁。
/**
 * Waits for this thread to die.
 *
 * <p> An invocation of this method behaves in exactly the same
 * way as the invocation
 *
 * <blockquote>
 * {@linkplain #join(long) join}{@code (0)}
 * </blockquote>
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */
public final void join() throws InterruptedException {
        join(0);
}
/**
 * Waits at most {@code millis} milliseconds for this thread to
 * die. A timeout of {@code 0} means to wait forever.
 *
 * <p> This implementation uses a loop of {@code this.wait} calls
 * conditioned on {@code this.isAlive}. As a thread terminates the
 * {@code this.notifyAll} method is invoked. It is recommended that
 * applications not use {@code wait}, {@code notify}, or
 * {@code notifyAll} on {@code Thread} instances.
 *
 * @param  millis
 *         the time to wait in milliseconds
 *
 * @throws  IllegalArgumentException
 *          if the value of {@code millis} is negative
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */
public final synchronized void join(long millis) throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;

    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }

    if (millis == 0) {
        while (isAlive()) {
            wait(0);
        }
    } else {
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

猜你喜欢

转载自www.cnblogs.com/Mao-admin/p/9988893.html