线程之间的通信可以通过共享内存变量的方式进行相互通信,也可以使用api提供的wait(),notify()实现线程之间的通信。wait()方法是Object类的方法,改方法用来将当前的线程置入"预执行队列"中,并且在wait()方法代码处停止执行进行等待,知道接收到同一个monitor对象的notify()或者notifyAll()方法的通知或者是收到中断。在调用wait()方法之前,线程必须获得锁,即只能在同步方法或者同步块中调用wait()方法,在执行wait()后,当前线程释放锁。在wait()方法释放锁后,其他处于等待该监视器对象的锁的线程将相互竞争获得此监视器对象锁。如果没有同步块或者同步方法,那么调用该方法将会抛IllegalMonitorStateException.并且由于可能发生中断或者是虚假唤醒,那么最好将wait()方法放在循环中调用。下面是api文档:

public final void wait()
throws InterruptedException
Causes the current thread to wait until another thread invokes the notify() method or the notifyAll() method for this object.
In other words, this method behaves exactly as if it simply performs the call wait(0).
The current thread must own this object's monitor.
The thread releases ownership of this monitor and waits until another thread notifies threads waiting on this object's monitor
to wake up either through a call to the notify method or the notifyAll method. The thread then waits
until it can re-obtain ownership of the monitor and resumes execution. As in the one argument version, interrupts and spurious wakeups are possible, and this method should always be used in a loop: synchronized (obj) {
while (<condition does not hold>)
obj.wait();
... // Perform action appropriate to condition
} This method should only be called by a thread that is the owner of this object's monitor.
See the notify method for a description of the ways in which a thread can become the owner of a monitor.
Throws:
IllegalMonitorStateException - if the current thread is not the owner of the object's monitor.
InterruptedException - if any thread interrupted the current thread before or while the current thread was waiting for a notification.
              The interrupted status of the current thread is cleared when this exception is thrown.
See Also:
notify(), notifyAll()

下面是wait(long timeout)的api:

public final void wait(long timeout)
throws InterruptedException
Causes the current thread to wait until either another thread invokes the notify() method or the notifyAll() method for this object, or a specified amount of time has elapsed.
The current thread must own this object's monitor. This method causes the current thread (call it T) to place itself in the wait set for this object and then to relinquish any and all synchronization claims on this object. Thread T becomes disabled for thread scheduling purposes and lies dormant until one of four things happens: Some other thread invokes the notify method for this object and thread T happens to be arbitrarily chosen as the thread to be awakened.
Some other thread invokes the notifyAll method for this object.
Some other thread interrupts thread T.
The specified amount of real time has elapsed, more or less. If timeout is zero, however, then real time is not taken into consideration and the thread simply waits until notified.
The thread T is then removed from the wait set for this object and re-enabled for thread scheduling. It then competes in the usual manner with other threads for the right to synchronize on the object; once it has gained control of the object, all its synchronization claims on the object are restored to the status quo ante - that is, to the situation as of the time that the wait method was invoked. Thread T then returns from the invocation of the wait method. Thus, on return from the wait method, the synchronization state of the object and of thread T is exactly as it was when the wait method was invoked.
A thread can also wake up without being notified, interrupted, or timing out, a so-called spurious wakeup. While this will rarely occur in practice, applications must guard against it by testing for the condition that should have caused the thread to be awakened, and continuing to wait if the condition is not satisfied. In other words, waits should always occur in loops, like this one: synchronized (obj) {
while (<condition does not hold>)
obj.wait(timeout);
... // Perform action appropriate to condition
} (For more information on this topic, see Section 3.2.3 in Doug Lea's "Concurrent Programming in Java (Second Edition)" (Addison-Wesley, 2000), or Item 50 in Joshua Bloch's "Effective Java Programming Language Guide" (Addison-Wesley, 2001).
If the current thread is interrupted by any thread before or while it is waiting, then an InterruptedException is thrown. This exception is not thrown until the lock status of this object has been restored as described above. Note that the wait method, as it places the current thread into the wait set for this object, unlocks only this object; any other objects on which the current thread may be synchronized remain locked while the thread waits. This method should only be called by a thread that is the owner of this object's monitor. See the notify method for a description of the ways in which a thread can become the owner of a monitor. Parameters:
timeout - the maximum time to wait in milliseconds.
Throws:
IllegalArgumentException - if the value of timeout is negative.
IllegalMonitorStateException - if the current thread is not the owner of the object's monitor.
InterruptedException - if any thread interrupted the current thread before or while the current thread was waiting for a notification. The interrupted status of the current thread is cleared when this exception is thrown.
See Also:
notify(), notifyAll()

方法notify()也要在同步方法或同步块中使用,该方法用来通知那些处于wait()方法的线程,并且这notify()和wait()方法的监视器对象应该是同一个,才可以进行唤醒。如果有多个线程处于wait()的等待中,那么也只能抽取一个进行唤醒,使wait()方法获取锁对象和继续执行。需要注意的是:在执行完notify()方法后,当前线程不会马上释放对象锁,wait()的线程也不会马上拥有锁。要等到notify()方法所在的线程将程序执行完成后,也就是退出同步快或者同步方法后才释放锁,继而wait()才获得对象锁。

也就是说:notify()操作可以唤醒一个因为调用了wait()方法而处于阻塞状态的线程,使其处于就绪状态。

下面是notify()api:

public final void notify()
Wakes up a single thread that is waiting on this object's monitor. If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation. A thread waits on an object's monitor by calling one of the wait methods.
The awakened thread will not be able to proceed until the current thread relinquishes the lock on this object. The awakened thread will compete in the usual manner with any other threads that might be actively competing to synchronize on this object; for example, the awakened thread enjoys no reliable privilege or disadvantage in being the next thread to lock this object. This method should only be called by a thread that is the owner of this object's monitor. A thread becomes the owner of the object's monitor in one of three ways: By executing a synchronized instance method of that object.
By executing the body of a synchronized statement that synchronizes on the object.
For objects of type Class, by executing a synchronized static method of that class.
Only one thread at a time can own an object's monitor. Throws:
IllegalMonitorStateException - if the current thread is not the owner of this object's monitor.
See Also:
notifyAll(), wait()

例子:

package soarhu;
class Service{ private Object lock; public Service(Object lock) {
this.lock = lock;
} void waiting(){
synchronized (lock){
System.out.println("waiting enter..."+System.currentTimeMillis());
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("waiting outer...."+System.currentTimeMillis());
}
} void notifying(){
synchronized (lock){
System.out.println("notifying enter..."+System.currentTimeMillis());
try {
Thread.sleep(3000);
lock.notify();
System.out.println("notifying outer..."+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} public class Test {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
Service service = new Service(o);
Thread t1 = new Thread(){
@Override
public void run() {
service.waiting();
}
};
Thread t2 = new Thread(){
@Override
public void run() {
service.notifying();
}
}; t1.start();
Thread.sleep(1000);
t2.start();
}
}

输出结果:

waiting enter...1492483779692
notifying enter...1492483780692
notifying outer...1492483783699
waiting outer....1492483783699

如果先启动t2线程那么就可能造成死锁。或者不能确保t1线程先启动的话,死锁必然发生。

下面修改notify()方法,验证notify()必须执行完后才释放锁

   void notifying(){
synchronized (lock){
System.out.println("notifying enter..."+System.currentTimeMillis());
try {
Thread.sleep(3000);
lock.notify();
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
System.out.println("notifying outer..."+System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

输出结果:

waiting enter...1492484010466
notifying enter...1492484011466
0
1
2
3
4
5
6
7
8
9
notifying outer...1492484014466
waiting outer....1492484014466

可以看到,即使notify()在循环前调用,但仍要使所在方法执行完成之后才释放监视器锁。

那么如何避免可能发生的死锁呢?修改代码如下:

package soarhu;
class Service{ private Object lock;
private boolean waitFirst = true; public Service(Object lock) {
this.lock = lock;
} void waiting(){
synchronized (lock){
while (waitFirst){
System.out.println("waiting enter..."+System.currentTimeMillis());
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("waiting outer...."+System.currentTimeMillis());
}
}
} void notifying(){
synchronized (lock){
System.out.println("notifying enter..."+System.currentTimeMillis());
try {
Thread.sleep(3000);
lock.notify();
for (int i = 0; i < 10; i++) {
System.out.println(i);
}
System.out.println("notifying outer..."+System.currentTimeMillis());
waitFirst=false;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} public class Test {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
Service service = new Service(o);
Thread t1 = new Thread(){
@Override
public void run() {
service.waiting();
}
};
Thread t2 = new Thread(){
@Override
public void run() {
service.notifying();
}
}; t2.start();
Thread.sleep(1000);
t1.start();
}
}

输出结果:

notifying enter...1492486139592
0
1
2
3
4
5
6
7
8
9
notifying outer...1492486142592

可以看到waitting()方法没有被执行了,从而避免了可能发生死锁问题,但是虽然避免了死锁那么wait/antify的意义也就不存在了。

notify()与notifyAll()区别

wait()表示:          放弃当前对资源的占有权,等啊等啊,一直等到有人通知我,我才会运行后面的代码。 
notify()表示:        当前的线程已经放弃对资源的占有,通知等待的线程来获得对资源的占有权,但是只有一个线程能够从wait状态中恢复, 然后继续运行wait()后面的语句; 
notifyAll()表示:     当前的线程已经放弃对资源的占有,通知所有的等待线程从wait()方法后的语句开始运行。

例子:

package other;
class Service{
private Object lock; public Service(Object lock) {
this.lock = lock;
} void waiting(){
synchronized (lock){
System.out.println("waiting enter...");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("waiting outer....");
}
} void notifying(){
synchronized (lock){
System.out.println("notifying enter...");
try {
Thread.sleep(1000);
lock.notify();
System.out.println("notifying outer...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
} public class Test {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
Service service = new Service(o);
for (int i = 0; i < 2; i++) {
new Thread(){
@Override
public void run() {
service.waiting();
}
}.start();
}
Thread.sleep(1000);
Thread t2 = new Thread(){
@Override
public void run() {
service.notifying();
}
};
t2.start();
}
}

输出结果:产生死锁。有一个没出来。

waiting enter...
waiting enter...
notifying enter...
notifying outer...
waiting outer....

改为notifyAll()后:

void notifying(){
synchronized (lock){
System.out.println("notifying enter...");
try {
Thread.sleep(1000);
lock.notifyAll();
System.out.println("notifying outer...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

输出结果:

waiting enter...
waiting enter...
notifying enter...
notifying outer...
waiting outer....

全部退出wait()方法。

notifyAll()/wait()中。if while的事情。

package soarhu;

import java.util.ArrayList;
import java.util.List; class Service{ private Object lock;
private List<String> list = new ArrayList<>(); public Service(Object lock) {
this.lock = lock;
} void waiting(){
synchronized (lock){
try {
if(list.size()==0){ //此处错误的用法,应该用while
System.out.println("wait enter..."+Thread.currentThread().getName());
lock.wait();
System.out.println("wait outer..."+Thread.currentThread().getName());
}
list.remove(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("size: "+list.size());
}
} void notifying(){
synchronized (lock){
System.out.println("ADD enter..."+Thread.currentThread().getName());
list.add("hello");
lock.notifyAll();
System.out.println("ADD outer..."+Thread.currentThread().getName());
}
}
} public class Test {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
Service service = new Service(o);
for (int i = 0; i < 2; i++) {
new Thread(){
@Override
public void run() {
service.waiting();
}
}.start();
}
Thread.sleep(1000);
for (int i = 0; i < 2; i++) {
new Thread(){
@Override
public void run() {
service.notifying();
}
}.start();
}
}
}

输出结果:抛出异常。。。

wait enter...Thread-0  //thread-0 等待
wait enter...Thread-1           //thread-1 等待
ADD enter...Thread-2  
Exception in thread "Thread-0" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
at java.util.ArrayList.rangeCheck(ArrayList.java:653)
at java.util.ArrayList.remove(ArrayList.java:492)
ADD outer...Thread-2
wait outer...Thread-1           //thread-1 退出 
size: 0
wait outer...Thread-0 //thread-0 退出,再此过程中抛出异常
at soarhu.Service.waiting(Test.java:23)
ADD enter...Thread-3
ADD outer...Thread-3
at soarhu.Test$1.run(Test.java:50) Process finished with exit code 0

分析结果:

开始线程0和线程2进入,判断得知list的长度为0则进行等待。然后线程2开始执行添加一个元素,并唤醒线程0和线程1.唤醒后,线程1执行remove()使list.size()为0.然后线程1从wait()方法处继续执行,再次执行remove.而此时size已经为0.所以会抛异常。而如果将if改为while,那么结果将正确,因为这样每次wait()被唤醒后每次都会检测size的大小。如果已经被别的线程修改过了,那么将继续wait().

 void waiting(){
synchronized (lock){
try {
while(list.size()==0){ //ok
System.out.println("wait enter..."+Thread.currentThread().getName());
lock.wait();
System.out.println("wait outer..."+Thread.currentThread().getName());
}
list.remove(0);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("size: "+list.size());
}
}

输出结果:

wait enter...Thread-0 //thread-0 等待
wait enter...Thread-1          //thread-1 等待
ADD enter...Thread-2            
ADD outer...Thread-2
wait outer...Thread-1          //thread-1 退出
size: 0
wait outer...Thread-0 //thread-0 退出
wait enter...Thread-0 //thread-0 继续等待
ADD enter...Thread-3
ADD outer...Thread-3
wait outer...Thread-0 //thread-0 退出
size: 0 Process finished with exit code 0

分析结果可知,

开始线程0和线程2进入,判断得知list的长度为0则进行等待。然后线程2开始执行添加一个元素,并唤醒线程0和线程1.唤醒后,线程1执行remove()使list.size()为0.然后线程1从wait()方法处继续执行,

此时再次进行size()判断,得知为0,继续等待。线程3进行加1,线程0现在才有机会退出等待。如果add的线程数少,那么又会发生死锁。改写代码:

public class Test {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
Service service = new Service(o);
for (int i = 0; i < 2; i++) { //这里的数量为2
new Thread(){
@Override
public void run() {
service.waiting();
}
}.start();
}
Thread.sleep(1000);
for (int i = 0; i < 1; i++) { //此时数量改为1
new Thread(){
@Override
public void run() {
service.notifying();
}
}.start();
}
}
}

输出结果:死锁

wait enter...Thread-0
wait enter...Thread-1
ADD enter...Thread-2
ADD outer...Thread-2
wait outer...Thread-1
size: 0
wait outer...Thread-0
wait enter...Thread-0

分析:可以对比上次的执行结果。线程0再次进入等待后,此时没有新的线程来通知他进行唤醒,即使唤醒了,那么如果list中没有元素依然要进行等待。

java多线程基本概述(五)——线程通信的更多相关文章

  1. Java多线程-同步:synchronized 和线程通信:生产者消费者模式

    大家伙周末愉快,小乐又来给大家献上技术大餐.上次是说到了Java多线程的创建和状态|乐字节,接下来,我们再来接着说Java多线程-同步:synchronized 和线程通信:生产者消费者模式. 一.同 ...

  2. java多线程学习-同步之线程通信

    这个示例是网上烂大街的,子线程循环100次,主线程循环50次,但是我试了很多次,而且从网上找了很多示例,其实多运行几次,看输出结果并不正确.不知道是我转牛角尖了,还是怎么了.也没有大神问,好痛苦.现在 ...

  3. java多线程回顾4:线程通信

    1.线程的协调运行 线程的协调运行有一个经典案例,即生产者和消费者问题. 假设有一个货架,生产者往货架上放货物,消费者从货架上取货物. 为了方便讲解,制定一个规则,生产者每放上一个货物,消费者就得取走 ...

  4. Java多线程(二) —— 线程安全、线程同步、线程间通信(含面试题集)

    一.线程安全 多个线程在执行同一段代码的时候,每次的执行结果和单线程执行的结果都是一样的,不存在执行结果的二义性,就可以称作是线程安全的. 讲到线程安全问题,其实是指多线程环境下对共享资源的访问可能会 ...

  5. “全栈2019”Java多线程第二十五章:生产者与消费者线程详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  6. “全栈2019”Java多线程第十五章:当后台线程遇到finally

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  7. Java 多线程基础(五)线程同步

    Java 多线程基础(五)线程同步 当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题. 要解决上述多线程并发访问一个资源的安全性问题,Java中提供了同步机制 ...

  8. Java多线程(一) —— 线程的状态详解

    一.多线程概述  1. 进程 是一个正在执行的程序.是程序在计算机上的一次运行活动. 每一个进程执行都有一个执行顺序.该顺序是一个执行路径,或者叫一个控制单元. 系统以进程为基本单位进行系统资源的调度 ...

  9. Java多线程学习(五)线程间通信知识点补充

    系列文章传送门: Java多线程学习(二)synchronized关键字(1) Java多线程学习(二)synchronized关键字(2) Java多线程学习(三)volatile关键字 Java多 ...

  10. java多线程详解(6)-线程间的通信wait及notify方法

    Java多线程间的通信 本文提纲 一. 线程的几种状态 二. 线程间的相互作用 三.实例代码分析 一. 线程的几种状态 线程有四种状态,任何一个线程肯定处于这四种状态中的一种:(1). 产生(New) ...

随机推荐

  1. [Bnuz OJ]1176 小秋与正方形

    传送门 问题描述 某天,acm的小秋拿到了一张很大很大的纸.他现在打算把它撕成正方 形.但是他没有任何工具,没有尺子,所以他尝试一种有趣的方法切分矩形.假设这是一个a*b的矩形(a>b),那么小 ...

  2. TypeScript设计模式之职责链、状态

    看看用TypeScript怎样实现常见的设计模式,顺便复习一下. 学模式最重要的不是记UML,而是知道什么模式可以解决什么样的问题,在做项目时碰到问题可以想到用哪个模式可以解决,UML忘了可以查,思想 ...

  3. node c++多线程插件 第二天 c++指针

    虽然取名叫node多线程插件,但是目前还是在学习c++的情况. 今天谈一谈c++指针. c++指针就像是c#中的引用变量,例如一个Person类的实例zs{Name="张三",Ag ...

  4. 初次接触java中的递归算法

    一道关于兔子繁衍的编程题: 有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第三个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少? 自己考虑了挺久,思路出现了问题,甚至连 ...

  5. Docker存储驱动之ZFS简介

    ZFS是下一代的文件系统,支持了很多存储高级特性,如卷管理.快照.和校验.压缩和重复删除技术.拷贝等. ZFS由Sun公司创建,现属于Oracle,ZFS是开源的,并基于CDDL license.因为 ...

  6. arcgisserver成功发布服务后,浏览服务,无地图显示

    软件:ArcMap10.2,ArcgisCatalog10.2 方法:ArcMap10.2添加数据库连接,成功登陆数据库后,拖拽目标图层至Map窗口,对各个图层进行符号化设置 ArcCatalog中找 ...

  7. 【转】SQL Server海量数据库的索引、查询优化及分页算法

    探讨如何在有着1000万条数据的MS SQL SERVER数据库中实现快速的数据提取和数据分页.以下代码说明了我们实例中数据库的“红头文件”一表的部分数据结构: CREATE TABLE [dbo]. ...

  8. 无图无定位新版css步骤条兼容ie6+

    <ul class="ui-step list-unstyled"> <li class="step-item"><b class ...

  9. jquery-scrollstop

    $(window) .on("scrollstart", function() { // Paint the world yellow when scrolling starts. ...

  10. 《Django By Example》第十一章 中文 翻译 (个人学习,渣翻)

    第十一章 缓存内容 (译者 @ucag 注:这是倒数第二章,最后一个项目即将完成. @夜夜月 将会接过翻译的最后一棒.这本书的翻译即将完成.这也是我翻译的最后一章,作为英语专业的学生,我对于翻译有了更 ...