1.多线程协调

synchronized解决了多线程竞争的问题,我们可以在synchronized块中安全的对一个变量进行修改,但是它没有解决多线程协调的问题。

例如设计一个TaskQueue,内部通过LinkedList<>()表示一个队列。addTask()将新任务放入队列,getTask()取出队列中的第一个任务。

下面这段代码的问题:当线程执行到getTask()内部的时候,如果队列为空,会导致死循环。该线程将会100%的占用CPU,而其他线程根本不能调用addTask()方法。这是因为此时对象的this锁已经被正在执行死循环的线程获得且不会释放。

class TaskQueue{
Queue<String> queue = new LinkedList<>();
public synchronized void addTask(String s){ //线程1不能执行,因为操作对象已经被线程2锁住了
this.queue.add(s);
}
public synchronized String getTask(){
while(queue.isEmpty()){ } //线程2执行getTask()时,如果队列为空,陷入死循环
return queue.remove();
}
}

预期效果:

  • 线程1通过addTask()不断往队列中添加任务,而线程2可以调用getTask()从队列中获取任务。
  • 如果队列为空,则getTask()应该等待,直到队列中至少有一个任务再执行getTask()。



    因此我们需要一种多线程协调的机制:当条件不满足时,线程进入等待状态。

2.wait

当一个线程执行getTask()时:

  • 首先获取锁。
  • 然后执行while条件判断:条件符合,进入等待状态。注意:进入等待状态以后,wait()方法不会返回,直到某个时刻,线程从等待状态被其他线程唤醒以后,wait()方法才会返回。
  • 之后线程继续执行下一条语句。

wait()方法的执行机制:

  • 首先,wait()不是一个普通的Java方法,而是定义在Object类上面的一个native方法,即是由JVM虚拟机的C代码实现的。
  • 其次,必须在synchronized代码块中才能调用wait()方法,因为wait()方法调用的时候,线程会释放它获得的锁,wait()方法返回后,线程又回重新获得锁。我们只能在锁对象调用wait()方法。此处我们使用synchronized修饰方法,所以我们获得对象是this对象。因此只能在this对象上调用wait()方法
  • 正是因为wait()方法会释放锁,所以其他的线程才能获得锁,并且进入addTask()方法。在addTask()方法内,我们向队列添加一个元素以后,就可以调用this.notify()来唤醒正在this对象上等待的线程,这样在wait()方法上等待的线程就可以被唤醒,然后从wait()方法返回以后继续执行。

完整的wait/notify机制:

3.示例

import java.util.LinkedList;
import java.util.Queue; class TaskQueue{
final Queue<String> queue = new LinkedList<>(); //定义一个LinkedList作为queue
public synchronized String getTask() throws InterruptedException{
System.out.println("开始执行");
while(this.queue.isEmpty()){ //判断queue是否是空,如果空,就释放对象锁,进入等待状态。
this.wait();
System.out.println("继续执行");
}
return queue.remove();//删除queue最上面的值
}
public synchronized void addTask(String name){
this.queue.add(name); //向queue添加一个任务
this.notifyAll(); //调用notifyALL()唤醒所有正在等待的线程
System.out.println("唤醒线程");
}
}
class WorkerThread extends Thread {
TaskQueue taskQueue;
public WorkerThread(TaskQueue taskqueue){
this.taskQueue = taskqueue;
}
public void run(){
while(!isInterrupted()){ //run()方法不断的执行getTask,获取taskQueue中的任务,一旦获取到,就打印hello name
String name;
try{
name = taskQueue.getTask();
}catch (InterruptedException e){
break;
}
String result = "Hello, "+name+"!";
System.out.println(result);
}
}
}
public class Main{
public static void main(String[] args) throws Exception{
TaskQueue taskqueue = new TaskQueue();
WorkerThread worker = new WorkerThread(taskqueue);
worker.start();
taskqueue.addTask("Bob");
Thread.sleep(1000);
taskqueue.addTask("Alice");
Thread.sleep(1000);
taskqueue.addTask("Tim");
Thread.sleep(1000);
worker.interrupt();//对worker线程调用interrupted方法让它中断
worker.join();//调用join()方法等待worker线程的结束
System.out.println("End");
}
}


## 4.问题
### 4.1为什么在while循环wait,而不是一个if语句中wait ?
真是因为很有可能有2个或者更多的线程在wait(),当它们从wait返回的时候,只有1个线程有机会获得this锁。这个线程会正确执行queue.remove()方法。
但是如果我们用if语句,当这个线程执行完毕以后,其他线程获得this锁以后继续执行remove方法,可能会因为队列为空而报错,其他线程又需要重新判断条件并可能再次进入wait状态。
所以通常在while循环中调用wait方法。

唤醒当前线程的不一定是执行添加队列的线程1。当非添加队列的线程1唤醒获取队列的线程2时:
* 使用while可以判断当前队列是否为空,
* 使用if继续执行,如果队列为空,执行remove方法会报错

4.2 为什么用notifyAll(),而不是notify()

因为notify只唤醒某一个等待的线程,而notifyAll会唤醒全部的等待线程。通常来说,notifyAll更安全。

有些时候如果我们的代码逻辑考虑不周,用notify会唤醒一个线程,而其他线程可能会永远等待下去,就醒不过来了。

所以要正确的编写多线程代码是非常困难的,需要考虑的条件非常多。

4.3 这个TaskQueue有什么意义呢

如果我们在编写一个浏览器,当我们解析html的时候,每遇到一个图片链接,我们就可以把链接放到TaskQueue中,而worker线程就可以不断的从Queue中取出链接,然后把print语句替换为下载图片的代码。这个程序可以看成它能够实现后台线程顺序下载多个图片的功能。

5.总结

  • 在synchronized内部可以调用wait()是线程进入等待状态
  • 必须在已获得的锁对象上调用wait()方法
  • 在synchronized内部可以调用notify/notifyAll()唤醒其他等待线程
  • 必须在已获得的锁对象上调用notify()/notifyAll()方法

廖雪峰Java11多线程编程-2线程同步-4wait和notify的更多相关文章

  1. 廖雪峰Java11多线程编程-2线程同步-3死锁

    1.线程锁可以嵌套 在多线程编程中,要执行synchronized块: 必须首先获得指定对象的锁 Java的线程锁是可重入的锁.对同一个对象,同一个线程,可以多次获取他的锁,即同一把锁可以嵌套.如以下 ...

  2. 廖雪峰Java11多线程编程-2线程同步-2synchronized方法

    1.Java使用synchronized对一个方法进行加锁 class Counter{ int count = 0; public synchronized void add(int n){ cou ...

  3. 廖雪峰Java11多线程编程-2线程同步-1同步代码块

    1.线程安全问题 多个线程同时运行,线程调度由操作系统决定,程序本身无法决定 如果多个线程同时读写共享变量,就可能出现问题 class AddThread extends Thread{ public ...

  4. 廖雪峰Java11多线程编程-1线程的概念-1多线程简介

    多任务 现代操作系统(windows,MacOS,Linux)都可以执行多任务: 多任务就是同时运行多个任务,例如同时开启钉钉.百度网盘.火狐.谷歌.ps等 操作系统执行多任务就是让多个任务交替执行, ...

  5. 廖雪峰Java11多线程编程-4线程工具类-1ThreadLocal

    多线程是Java实现多任务的基础: Thread ExecutorService ScheduledThreadPool Fork/Join Thread对象代表一个线程:调用Tread.curren ...

  6. 廖雪峰Java11多线程编程-1线程的概念-2创建新线程

    Java语言内置多线程支持: 一个Java程序实际上是一个JVM进程 JVM用一个主线程来执行main()方法 在main()方法中又可以启动多个线程 1.创建新线程 1.1 方法一:使用Thread ...

  7. 廖雪峰Java11多线程编程-1线程的概念-5中断线程

    1.中断线程: 如果线程需要执行一个长时间任务,就可能需要中断线程.场景:从网络上下载一个100M的文件,用户在下载过程中中断下载任务的执行. 中断线程就是其他线程给该线程发一个信号,该线程收到信号后 ...

  8. 廖雪峰Java11多线程编程-1线程的概念-3线程的状态

    1线程的状态 线程终止的的原因: run()或call()方法执行完成,线程正常结束 线程抛出一个未捕获的Exception或Error 直接调用该线程的stop()方法来结束该线程--该方法容易导致 ...

  9. 廖雪峰Java11多线程编程-3高级concurrent包-4Concurrent集合

    Concurrent 用ReentrantLock+Condition实现Blocking Queue. Blocking Queue:当一个线程调用getTask()时,该方法内部可能让给线程进入等 ...

随机推荐

  1. 数据结构C++版-队列

    一.概念 分类: 二.补充说明 1.<面向对象的队列设计>课程问答: 首先要明确数据结构和数据存储结构的概念. 数据结构是指数据对象之间的逻辑关系,例如二叉树,队列,栈等,而数据存储结构是 ...

  2. hive表操作(转)

    转载于:http://blog.csdn.net/lovelovelovelovelo/article/details/52234971 数据类型 基本数据类型 集合类型,array.map.stru ...

  3. Git及github使用(一)客户端与本地关联

    一.下载安装git客户端 1.官网下载地址:https://git-scm.com/ 2.选择自己相应的系统进行下载即可(windows一路默认安装) 二.配置git 1.在github上注册自己的账 ...

  4. python 18 函数基础二

    转自 http://www.cnblogs.com/BeginMan/p/3173328.html 一.装饰器(decorators) 装饰器的语法以@开头,接着是装饰器函数的名字.可选参数. 紧跟装 ...

  5. POJ 2763 /// 基于边权的树链剖分

    题目大意: 给定n个结点,有n-1条无向边,给定每条边的边权 两种操作,第一种:求任意两点之间路径的权值和,第二种:修改树上一点的权值. 因为是一棵树,可以直接把 u点和v点间(假设u为父节点,v为子 ...

  6. 【校OJ】选网线

    暑假学校OJ上的题目. 一道很有意思的二分. 题意:三个数组,每个数组各选一个数出来看是否能组成目标数. 题解:前两个数组两两的和组合一下,二分第三个数组,找是否能组成目标数. 代码: #includ ...

  7. POJ1149_PIGS(网络流/EK)

    PIGS Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 15721   Accepted: 7021 Description ...

  8. ASP.NET Core Web应用在发布时选择是否对视图进行编译

    原文:ASP.NET Core Web应用在发布时选择是否对视图进行编译 在我们发布ASP.NET Core Web应用程序时,选择以文件形式发布,发布方法选择文件系统 默认情况下,会把Views的视 ...

  9. SVG动画制作工具 , 从此抛弃臃肿的gif

    VG简介 只要是程序员的你,你不得不知道svg图片,它可以无限任意放大拉伸都不会损失画质,就像系统字体一样可以无限矢量放大,svg更高级是可以用来制作矢量动画,现在各大浏览器和系统基本对svg已经支持 ...

  10. java_MySQL未整理

    package cn.aikang.MySql; public class MySqlTest {/*数据库的基本概念: 1.数据库的英文单词:DataBase简称:DB 2.什么是数据库:用于存储和 ...