谈谈JDK线程的伪唤醒
在JDK的官方的wait()方法的注释中明确表示线程可能被“虚假唤醒“,JDK也明确推荐使用while来判断状态信息。那么这种情况的发生的可能性有多大呢?
使用生产者消费者模型来说明,伪唤醒造成的后果是本来未被唤醒的线程被唤醒了,那么就破坏了生产者消费者中的判断条件,也就是例子中的while条件number == 0或者number == 1。最终导致的结果就死0和1不能交替出现。
JDK的两种同步方案均可能出现这种伪唤醒的问题(API说明明确表示会出现这种现象),这两种组合是synchronized+wait+notify和ReentrantLock+await+signal。下面的例子中,如果把while换成if,那么就0、1就不能交替出现,反制则会,例子中是100个线程进行增加,100个线程进行减少。
在Java并发编程书上面引用了Thinking In Java的一句话说,大概意思是:任何并发编程都是通过加锁来解决。其实JDK并发编程也是通过加锁解决,每个对象都有一个对象锁,并且有一个与这个锁相关的队列,来实现并发编程。区别在于加锁的粒度问题,读读可以并发(在读远大于写的场景下比较合适),其他三种情况不能并发。而关系型数据库也是利用这一思想,只不过做得更加彻底,除了写写不能并发,其他三种情况都能并发,这得益于MVCC模型。
public class Resource {
public int number = 0;
public synchronized void add() {
while (number == 1) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number++;
System.err.println(number + "-" + Thread.currentThread().getId());
notifyAll();
}
public synchronized void minus() {
while (number == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number--;
System.err.println(number + "-" + Thread.currentThread().getId());
notifyAll();
}
public static class AddThread implements Runnable {
private Resource resource;
public AddThread(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
for (;;) resource.add();
}
}
public static class MinusThread implements Runnable {
private Resource resource;
public MinusThread(Resource resource) {
this.resource = resource;
}
@Override
public void run() {
for (;;) resource.minus();
}
}
public static void main(String[] args) {
Resource resource = new Resource();
for (int i = 0; i < 100; i++) {
new Thread(new AddThread(resource)).start();
new Thread(new MinusThread(resource)).start();
}
}
}
public class ResourceLock {
private int number = 0;
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
public int getNumber() {
return this.number;
}
public void increase() {
lock.lock();
try {
while (number == 1) {
condition.await();
}
number++;
System.err.println(number + "-" + Thread.currentThread().getId());
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
public void decrease() {
lock.lock();
try {
while (number == 0) {
condition.await();
}
number--;
System.err.println(number + "-" + Thread.currentThread().getId());
condition.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
static class IncreaseThread implements Runnable {
private ResourceLock resource;
public IncreaseThread(ResourceLock resource) {
this.resource = resource;
}
@Override
public void run() {
for (;;) resource.increase();
}
}
static class DecreaseThread implements Runnable {
private ResourceLock resource;
public DecreaseThread(ResourceLock resource) {
this.resource = resource;
}
@Override
public void run() {
for (;;) resource.decrease();
}
}
public static void main(String[] args) throws Exception {
ResourceLock resource = new ResourceLock();
ExecutorService es = Executors.newFixedThreadPool(5);
for (int i = 0; i < 100; i++) {
es.submit(new IncreaseThread(resource));
es.submit(new DecreaseThread(resource));
}
}
}
谈谈JDK线程的伪唤醒的更多相关文章
- 多线程 - 线程通信 suspend-resume wait-notify park-unpark 伪唤醒
线程通信(如 线程执行先后顺序,获取某个线程执行的结果等)有多种方式: 文件共享 线程1 --写入--> 文件 < --读取-- 线程2 网络共享 变量共享 线程1 --写入--> ...
- Java多线程系列--“基础篇”05之 线程等待与唤醒
概要 本章,会对线程等待/唤醒方法进行介绍.涉及到的内容包括:1. wait(), notify(), notifyAll()等方法介绍2. wait()和notify()3. wait(long t ...
- java 多线程—— 线程等待与唤醒
java 多线程 目录: Java 多线程——基础知识 Java 多线程 —— synchronized关键字 java 多线程——一个定时调度的例子 java 多线程——quartz 定时调度的例子 ...
- Java多线程(五)——线程等待与唤醒
一.wait().notify().notifyAll()等方法介绍 在Object.java中,定义了wait(), notify()和notifyAll()等接口.wait()的作用是让当前线程进 ...
- java - 线程等待与唤醒
Java多线程系列--“基础篇”05之 线程等待与唤醒 概要 本章,会对线程等待/唤醒方法进行介绍.涉及到的内容包括:1. wait(), notify(), notifyAll()等方法介绍2. w ...
- java 多线程系列基础篇(五)之线程等待与唤醒
1.wait(), notify(), notifyAll()等方法介绍 在Object.java中,定义了wait(), notify()和notifyAll()等接口.wait()的作用是让当前线 ...
- Java:谈谈控制线程的几种办法
目录 Java:谈谈控制线程的几种办法 join() sleep() 守护线程 主要方法 需要注意 优先级 弃用三兄弟 stop() resume suspend 中断三兄弟 interrupt() ...
- jdk线程池ThreadPoolExecutor工作原理解析(自己动手实现线程池)(一)
jdk线程池ThreadPoolExecutor工作原理解析(自己动手实现线程池)(一) 线程池介绍 在日常开发中经常会遇到需要使用其它线程将大量任务异步处理的场景(异步化以及提升系统的吞吐量),而在 ...
- jdk线程池主要原理
本文转自:http://blog.csdn.net/linchengzhi/article/details/7567397 正常创建一个线程的时候,我们是这样的:new thread(Runnable ...
随机推荐
- [CareerCup] 15.7 Student Grade 学生成绩
15.7 Imagine a simple database storing information for students' grades. Design what this database m ...
- js事件冒泡和事件捕获的区别
- winform制作简单计算器
public Form1() { InitializeComponent(); textBox2.Text = ";//主显示屏 textBox1.Text = "";/ ...
- mysql错误代码整理
1005:创建表失败1006:创建数据库失败1007:数据库已存在,创建数据库失败1008:数据库不存在,删除数据库失败1009:不能删除数据库文件导致删除数据库失败1010:不能删除数据目录导致删除 ...
- List集合对象根据字段排序
//把需要比较的对象实现Comparable接口实现compareTo方法 public class Address implements Comparable<Address> { St ...
- Android studio快捷键总结
功能 快捷键 功能 快捷键 格式化代码 command + alt + L 运行程序到设备 Ctrl + R 导包.修正 alt + enter 运行Debug到设备 Ctrl + D 复 ...
- Spring中的单例一二
Spring框架很好的帮助我们创建和管理dao.bean.service.action等对象, 但是它创建的对象是单例呢还是多例,又有哪些区别以及为什么 1.在Spring中默认创建的是单例模式,简单 ...
- JSP页面编码问题
<%@ page language="java" contentType="text/html; charset=gb2312" pageEncoding ...
- svn服务配置和日常维护命令
Subversion独立服务和与apache整合服务. 一 .Svn独立服务安装 操作系统: Redhat Linux AS3 AS 4 ContOS AS 4 安装包获取: 下载[url]ht ...
- Python脚本模拟登录网页之CSDN篇
1. 通过Firefox配合插件Tamper Date获取登录时客户端向服务器端提交的数据, 并且发现lt和execution这两个字段每次登录时都不一样. POSTDATA=username=you ...