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

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

1. 多线程通信:

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

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

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

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

   public class ListAdd1 {
private volatile static List list = new ArrayList(); public void add(){
list.add("bjsxt");
}
public int size(){
return list.size();
} public static void main(String[] args) { final ListAdd1 list1 = new ListAdd1(); Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
for(int i = 0; i <10; i++){
list1.add();
System.out.println("当前线程:" + Thread.currentThread().getName() + "添加了一个元素..");
Thread.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1"); Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
while(true){
if(list1.size() == 5){
System.out.println("当前线程收到通知:" + Thread.currentThread().getName() + " list size = 5 线程停止..");
throw new RuntimeException();
}
}
}
}, "t2"); t1.start();
t2.start();
}
}

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

示例:

   import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.LinkedBlockingQueue;
/**
* wait notfiy 方法,wait释放锁,notfiy不释放锁
* @@author Maozw
*
*/
public class ListAdd2 {
private volatile static List list = new ArrayList(); public void add(){
list.add("bjsxt");
}
public int size(){
return list.size();
} public static void main(String[] args) { final ListAdd2 list2 = new ListAdd2(); // 1 实例化出来一个 lock
// 当使用wait 和 notify 的时候 , 一定要配合着synchronized关键字去使用
//final Object lock = new Object(); final CountDownLatch countDownLatch = new CountDownLatch(1); Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
//synchronized (lock) {
for(int i = 0; i <10; i++){
list2.add();
System.out.println("当前线程:" + Thread.currentThread().getName() + "添加了一个元素..");
Thread.sleep(500);
if(list2.size() == 5){
System.out.println("已经发出通知..");
countDownLatch.countDown();
//lock.notify();
}
}
//}
} catch (InterruptedException e) {
e.printStackTrace();
} }
}, "t1"); Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
//synchronized (lock) {
if(list2.size() != 5){
try {
//System.out.println("t2进入...");
//lock.wait();
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("当前线程:" + Thread.currentThread().getName() + "收到通知线程停止..");
throw new RuntimeException();
//}
}
}, "t2"); t2.start();
t1.start(); }
}

问题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,从而有机会获得锁。
 import java.util.ArrayList;
import java.util.List; public class Something {
private Buffer mBuf = new Buffer(); public void produce() {
synchronized (this) {
while (mBuf.isFull()) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
mBuf.add();
notifyAll();
}
} public void consume() {
synchronized (this) {
while (mBuf.isEmpty()) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
mBuf.remove();
notifyAll();
}
} private class Buffer {
private static final int MAX_CAPACITY = 1;
private List innerList = new ArrayList<>(MAX_CAPACITY); void add() {
if (isFull()) {
throw new IndexOutOfBoundsException();
} else {
innerList.add(new Object());
}
System.out.println(Thread.currentThread().toString() + " add"); } void remove() {
if (isEmpty()) {
throw new IndexOutOfBoundsException();
} else {
innerList.remove(MAX_CAPACITY - 1);
}
System.out.println(Thread.currentThread().toString() + " remove");
} boolean isEmpty() {
return innerList.isEmpty();
} boolean isFull() {
return innerList.size() == MAX_CAPACITY;
}
} public static void main(String[] args) {
Something sth = new Something();
Runnable runProduce = new Runnable() {
int count = 4; @Override
public void run() {
while (count-- > 0) {
sth.produce();
}
}
};
Runnable runConsume = new Runnable() {
int count = 4; @Override
public void run() {
while (count-- > 0) {
sth.consume();
}
}
};
for (int i = 0; i < 2; i++) {
new Thread(runConsume).start();
}
for (int i = 0; i < 2; i++) {
new Thread(runProduce).start();
}
}
}

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()主线程。例如:
 package com.maozw.springmvc.controller;

 import java.util.Date;
import java.util.concurrent.TimeUnit; public class JoinTest implements Runnable { private String name; public JoinTest(String name) {
this.name = name;
} public void run() {
System.out.printf("%s begins: %s\n", name, new Date());
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.printf("%s has finished: %s\n", name, new Date());
} public static void main(String[] args) {
Thread thread1 = new Thread(new JoinTest("One"));
Thread thread2 = new Thread(new JoinTest("Two"));
thread1.start();
thread2.start(); try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Main thread is finished");
}
}

输出结果

 One begins: Mon Jul 23 22:41:21 CST 2018
Two begins: Mon Jul 23 22:41:21 CST 2018
Two has finished: Mon Jul 23 22:41:25 CST 2018
One has finished: Mon Jul 23 22:41:25 CST 2018
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;
}
}
}

最新文章

  1. 用SignalR实现实时查看WebAPI请求日志
  2. 关于DButils的简单介绍
  3. 05——C++自己合成的函数
  4. CentOS6.4-RMAN定时任务备份 on 11GR2
  5. counting sort 计数排序
  6. UVa 1401 (Tire树) Remember the Word
  7. 【转】int const A::func()和int A::func() const
  8. 多线程、多任务管理 简单demo
  9. 初探响应式Web设计
  10. hudson--ant编写记录
  11. 如何在Intellij IDEA中拉svn分支?
  12. iOS的settings bundle中开关按钮(Toggle Switch)取不到值的问题
  13. 『性能』ServiceStack.Redis 和 StackExchange.Redis 性能比较
  14. JavaScript大杂烩9 - 理解BOM
  15. E - Evaluate Matrix Sum
  16. linux命令(45):去掉 所有文件中的空行
  17. commons-beanutils的使用
  18. k8s1.4.3安装实践记录(1)-etcd、docker、flannel安装配置
  19. eventql部署过程
  20. python 之sqlite3库学习

热门文章

  1. mysql之general log 日志
  2. orm的设计思路
  3. 已知两个int变量a、b,定义4个方法分别对变量a、b进行加减乘除运算,并测试结果。
  4. 深入理解java虚拟机(1)走进jvm
  5. setTimeout定时器
  6. 关于 i++ 和 ++ i
  7. Ubuntu 双网卡route
  8. zabbix 内存溢出
  9. AUC计算方法
  10. 如何配置SQL Server数据库远程连接