内置锁(二)synchronized下的等待通知机制
一、等待/通知机制的简介
线程之间的协作:
为了完成某个任务,线程之间需要进行协作,采取的方式:中断、互斥,以及互斥上面的线程的挂起、唤醒;如:生成者--消费者模式、或者某个动作完成,可以唤醒下一个线程、管道流已准备等等;
等待/通知机制:
等待/通知机制 是线程之间的协作一种常用的方式之一,在显示锁Lock 和 内置锁synchronized都有对应的实现方式。
等待/通知机制 经典的使用方式,便是在生产者与消费者的模式中使用:
1、生产者负责生产商品,并送到仓库中存储;
2、消费者则从仓库中获取商品,享受商品;
3、仓库的容量有限,当仓库满了后,生产者就会停止生产,进入等待状态。直到消费者消耗了一部分商品,通知生产者,仓库有空位了,生产者才会继续生产商品;
4、当消费者的消耗速度过快,消耗光仓库的商品时,也会停止消耗,进入等待状态,直到生产者生产了商品,并通知唤醒消费者时,消费者才继续消耗商品。
二、等待/通知机制 在synchronized下的实现:
在内置锁下,等待/通知机制 是依赖于wait( )和 notify、notifyAll 来实现的,这三个方法都是继承自根类Object中。
下面是这几个方法的API描述,wait()方法有3个版本:
void wait():
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。
void wait(long timeout):
超时等待,在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。如果时间当时间到来了,线程还是没有被唤醒或中断,那么就会自动唤醒;
void wait(long timeout, int nanos):
参数timeout的单位是毫秒,参数nanos的单位是纳秒,即等待时间精确到纳秒。其他特性与wait(long timeout)一样。
void notify():
唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争;例如,唤醒的线程在作为锁定此对象的下一个线程方面没有可靠的特权或劣势。
void notifyAll():
唤醒在此对象监视器上等待的所有线程。
1、调用 wait、notify、notifyAll 前,必须先获取锁
在调用 wait、notify、notifyAll 前,必须获取锁(对象监视器),而且这三个方法是由synchronized的对象锁(对象监视器)来调用。对象监视器 中维护着两个队列:就绪队列,等待队列。调用了wait()方法就是使线程进入等待队列,不再参与锁的竞争,直到被唤醒才能重新进入就绪队列,才可能获取锁,再次被执行。
如果在没有获取锁(对象监视器)的情况下,将会抛出IllegalMonitorStateException 异常:
wait、notify、notifyAll 方法只应由作为此对象监视器的所有者的线程来调用。通过以下三种方法之一,线程可以成为此对象监视器的所有者:
- 通过执行此对象的同步实例方法。
- 通过执行在此对象上进行同步的 synchronized 语句的正文。
- 对于 Class 类型的对象,可以通过执行该类的同步静态方法。
一次只能有一个线程拥有对象的监视器。
抛出:
IllegalMonitorStateException - 如果当前线程不是此对象监视器的所有者。
看下面的例子,消费者想要10个以上的商品,如果不够,则等待生产者生产足够的商品,生产者再来唤醒消费者线程。
//生产者与消费者互斥使用仓库
public static List<String> warehouse = new LinkedList<>();
public static void main(String[] args) {
//生产者线程
Thread thread_1 = new Thread(){
@Override
public void run() {
//对象监视器为warehouse,必须先获取这个对象监视器
synchronized ( warehouse) {
int i = 0;
//生产10个商品
while(warehouse.size()<=10){
++i;
//生产商品,添加进仓库
warehouse.add("生产了商品goods"+i);
//当商品数量足够时,便唤醒消费者线程
if(warehouse.size()>=10){
warehouse.notify();
//生产任务完成,跳出循环,结束运行,从而可以释放锁
break;
}
}
}
}
};
//消费者线程
Thread thread_2 = new Thread(){
@Override
public void run() {
synchronized (warehouse) {
try {//如果仓库的商品数量不能满足消费者
if(warehouse.size()<10){
//消费者进入等待队列,等待被唤醒
warehouse.wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
//消费商品
for(String goods:warehouse){
System.out.println("消费者消费了商品:"+goods);
}
}
}
};
//
thread_2.start();
thread_2.setPriority(Thread.MAX_PRIORITY);
thread_1.start();
}
运行结果:
消费者消费了商品:商品goods1
消费者消费了商品:商品goods2
消费者消费了商品:商品goods3
消费者消费了商品:商品goods4
消费者消费了商品:商品goods5
消费者消费了商品:商品goods6
消费者消费了商品:商品goods7
消费者消费了商品:商品goods8
消费者消费了商品:商品goods9
消费者消费了商品:商品goods10
2、wait()方法是可以被中断的
如果线程在等待过程中被中断,那么线程的等待状态就会被打断,即由对象监视器的等待队列进入就绪队列;所以,可以总结一下,线程在等待状态被唤醒的情况:
- 被其他线程调用notify、notifyAll方法唤醒;
- 如果是超时等待,那么时间到来了,线程也会自动唤醒。
- 有中断发生,线程的等待状态被打断,强制唤醒。
public static void main(String[] args) throws InterruptedException {
final String lock = "lock";
Thread thread = new Thread(){
@Override
public void run() {
synchronized (lock) {
System.out.println("我即将进入等待状态!");
try {
//超时等待5秒
lock.wait(5000);
} catch (InterruptedException e) {
System.out.println("啊!我被中断了");
e.printStackTrace();
}
System.out.println("我在运行中....");
}
}
};
thread.start();
//main线程睡眠1秒后,中断线程thread
Thread.sleep(1000);
thread.interrupt();
}
运行结果:
线程thread因为中断,还没等待5秒,就被提前唤醒。
3、wait()是释放锁,notify、notifyAll 不释放锁
调用wait()方法是线程马上释放锁,进入等待队列,而调用notify、notifyAll 后,线程不会释放锁,而仅仅唤醒线程而已,要等待当前运行完后,锁才是真的被释放,被唤醒的线程才可以竞争获取锁。
final String lock = "lock";
Thread thread_1 = new Thread("thread_1"){
@Override
public void run() {
synchronized (lock) {
System.out.println(getName()+"进入等待状态");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+"被唤醒,继续运行...");
}
}
};
thread_1.start();
Thread thread_2 = new Thread("thread_2"){
@Override
public void run() {
synchronized (lock) {
System.out.println(getName()+"在运行...");
try {
//模拟占用着锁,运行了一秒,sleep在睡眠中是不会释放锁的
Thread.sleep(1000);
//在调用notifyAll方法前,线程1的状态
System.out.println("线程"+thread_1.getName()+"被唤醒前的状态是:"+thread_1.getState());
//唤醒在对象监视器的等待队列的所有线程
lock.notifyAll();
//模拟占用着锁,继续运行5次,约为5秒
int i = 0;
while(i<5){
System.out.println("线程"+thread_1.getName()+"的状态是:"+thread_1.getState());
sleep(1000);
i++;
}
System.out.println("线程"+getName()+"运行结束!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
};
thread_2.start();
运行结果:
thread_1进入等待状态
thread_2在运行...
线程thread_1被唤醒前的状态是:WAITING
线程thread_1的状态是:BLOCKED
线程thread_1的状态是:BLOCKED
线程thread_1的状态是:BLOCKED
线程thread_1的状态是:BLOCKED
线程thread_1的状态是:BLOCKED
线程thread_2运行结束!
thread_1被唤醒,继续运行...
三、易混淆的几个方法的区别
1、wait( )方法:
- 先释放锁,再进入等待状态;
- 使当前线程暂停运行,让出CPU,不再参与CPU的调度,直到被唤醒为止;
2、sleep()方法:
- 在睡眠过程中,不释放锁;
- 使当前线程暂停运行,让出CPU,不再参与CPU的调度,直到睡眠时间到来;
3、yield( )方法:
- 不释放锁
- 使当前线程暂停运行,给同等优先级或高优先级的线程让出CPU。但有可能让出CPU失败,因为调用yield( )方法后,当前线程由运行状态,变成就绪状态,会马上参与CPU的调度,也就说可能再次被调度,当前线程依旧占用着CPU。
内置锁(二)synchronized下的等待通知机制的更多相关文章
- 深入理解Java内置锁和显式锁
synchronized and Reentrantlock 多线程编程中,当代码需要同步时我们会用到锁.Java为我们提供了内置锁(synchronized)和显式锁(ReentrantLock)两 ...
- 七 内置锁 wait notify notifyall; 显示锁 ReentrantLock
Object中对内置锁进行操作的一些方法: Java内置锁通过synchronized关键字使用,使用其修饰方法或者代码块,就能保证方法或者代码块以同步方式执行. 内置锁使用起来非常方便,不需要显式的 ...
- 显式锁(四)Lock的等待通知机制Condition
任意一个Java对象,都拥有一组监视器方法(定义在根类Object上),主要包括:wait( ).wait(long timeout).notify().notifyAll()方法:这些方法与关 ...
- 【Java并发基础】使用“等待—通知”机制优化死锁中占用且等待解决方案
前言 在前篇介绍死锁的文章中,我们破坏等待占用且等待条件时,用了一个死循环来获取两个账本对象. // 一次性申请转出账户和转入账户,直到成功 while(!actr.apply(this, targe ...
- Java并发读书笔记:线程通信之等待通知机制
目录 synchronized 与 volatile 等待/通知机制 等待 通知 面试常问的几个问题 sleep方法和wait方法的区别 关于放弃对象监视器 在并发编程中,保证线程同步,从而实现线程之 ...
- 内置锁(一)synchronized 介绍与用法
一.synchronized 的介绍 synchronized 是 Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码,而这段代码也被称 ...
- Java 并发:内置锁 Synchronized
摘要: 在多线程编程中,线程安全问题是一个最为关键的问题,其核心概念就在于正确性,即当多个线程訪问某一共享.可变数据时,始终都不会导致数据破坏以及其它不该出现的结果. 而全部的并发模式在解决问题时,採 ...
- 深入理解java内置锁(synchronized)和显式锁(ReentrantLock)
多线程编程中,当代码需要同步时我们会用到锁.Java为我们提供了内置锁(synchronized)和显式锁(ReentrantLock)两种同步方式.显式锁是JDK1.5引入的,这两种锁有什么异同呢? ...
- Java内置锁synchronized的实现原理及应用(三)
简述 Java中每个对象都可以用来实现一个同步的锁,这些锁被称为内置锁(Intrinsic Lock)或监视器锁(Monitor Lock). 具体表现形式如下: 1.普通同步方法,锁的是当前实例对象 ...
随机推荐
- / is not a valid selector
- easyui学习笔记10—手风琴格子始终展开和多个格子展开
始终打开有时候可能会很管用,其实就是一个设置问题.这里就不再介绍引用的资源了,这里只看看html是怎么写的. 1.html代码 <body> <h2>Basic Accordi ...
- 如何用移动硬盘安装win7 系统
身边没有U盘和光盘,就只有一个移动硬盘.移动硬盘安装系统是怎么进行的.在这里小毛孩来给大家上一课. 前期准备: 1.移动硬盘. 2.win7 32位的操作系统(*.iso). 3.有系统且可开机的电脑 ...
- Hibernate4.3配置
<?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hi ...
- HDU 4035 Maze 概率DP 搜索
解题报告链接: http://www.cnblogs.com/kuangbin/archive/2012/10/03/2711108.html 先推公式,设计状态,令DP[i]表示在房间i退出要走步数 ...
- jquery学习1之对juery对象的细节操作1
jquery是前台动态页面开发的一个很重要的工具. 一:jquery对象中length属性和size()方法 var a=$("a").length; var b= ...
- CUDA npp运动检测模块性能测试
测试环境: Cpu: Intel(R)Core(TM)i7-4790 CPU @3.6GHZ GPU: NVIDIA GeForce GTX960 *2 操作系统: Wi ...
- gitlab操作
一.初始设置 在某一个具体的project下: 1.gitlab中删除一个工程Setting-->General-->Advanced settings-->RemoveProjec ...
- vulcanjs 核心架构概念
基于包的架构 为了保证系统的灵活以及可扩展,vulcanjs 使用基于包的架构设计,每一个功能都是一个包,可以方便的添加,移除 扩展.而不是修改 vulcan 的设计哲学是进行系统扩展,而不是编辑修改 ...
- ubuntu 14.04安装OVS虚拟OpenFlow交换机配置总结
一.安装OVS sudo apt-get install openvswitch-controller openvswitch-switch openvswitch-datapath-source ( ...