Java并发编程(八)线程间协作(上)
多线程并发执行时,不同的线程执行的内容之间可能存在一些依赖关系,比如线程一执行a()方法和c()方法,线程二执行b()方法,方法a()必须在方法b()之前执行,而方法c()必须在方法b()之后执行。这时两个线程之间就需要协作才能完成这个任务,使两个线程协作有一个简单粗暴的方法,即监控布尔变量,代码如下:
boolean finishA = false;
boolean finishB = false;
线程一执行下面的代码:
a();
finishA = true;
while(!finishB){}
c();
线程二执行下面的代码:
while(!finishA){}
b();
finishB = true;
在执行b()方法和c()方法时都会检查依赖的方法是否执行结束,只有依赖的方法执行结束才跳出循环。这种方法的优点是简单粗暴,缺点是在等待依赖的方法时线程处于忙等待的状态,即线程处于运行状态(占用CPU时间)但是没有做任何有实际意义的东西,更好的办法是在线程等待时将其阻塞,阻塞状态时不占用CPU时间,从而提高CPU的利用率。
使用内置锁协作
Java提供了线程间合作的机制,即Object.wait()方法、Object.notify()和Object.notifyAll()方法。
wait()方法:使当前线程阻塞,等待其它线程调用notify()方法,释放当前获取的锁。
notify()方法:唤醒一个等待着的线程,这个线程唤醒之后尝试获取锁,其它线程继续等待。
notifyAll()方法:唤醒所有等待着的线程尝试获取锁,这些线程排队等待锁。
使用这些方法举个小例子,学生去食堂吃饭的时候先取一碗,然后把碗交给盛饭阿姨,阿姨盛完饭把碗还给同学,这时候同学就可以吃饭了,用代码模拟这个例子如下:
class Student implements Runnable {
public void run() {
synchronized(CafeteriaTest.wan) {
System.out.println("学生:取到了一个碗");
System.out.println("学生:阿姨帮忙盛饭");
CafeteriaTest.wan.notify();
try {
CafeteriaTest.wan.wait();
} catch (InterruptedException e) { }
System.out.println("学生:吃饭");
}
}
}
class CafeteriaWorker implements Runnable {
public void run() {
synchronized(CafeteriaTest.wan) {
try {
CafeteriaTest.wan.wait();
} catch (InterruptedException e) { }
System.out.println("阿姨:给学生盛饭");
CafeteriaTest.wan.notify();
}
}
}
public class CafeteriaTest {
public static Object wan = new Object();
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new CafeteriaWorker());
Thread.sleep(100);//等阿姨准备好
exec.execute(new Student());
exec.shutdown();
}
}
输出结果如下:
学生:取到了一个碗
学生:阿姨帮忙盛饭
阿姨:给学生盛饭
学生:吃饭
例子中先创建了一个“阿姨线程”,这个线程先获取“碗”的锁,然后调用了wait()方法进入阻塞状态并释放了锁。接着我们创建了“学生线程”,学生先打印取到了碗,然后调用notify()方法通知“阿姨线程”盛饭,并且调用wait()方法使当前线程释放锁并阻塞;随后“阿姨线程”从阻塞状态恢复为学生打饭,然后“阿姨线程”调用notify()方法通知学生打完饭了,“阿姨线程”运行结束并释放了锁,“学生线程”拿到了“碗的锁”开始吃饭。
在这个过程中有三点需要注意:
1.在调用wait()和notify()方法之前必须使用synchronized关键字获取这个对象的锁,否则系统会抛异常。因此不能在使用显示锁的临界区内调用这些方法。
2.调用wait()方法之后有两个因素阻止线程执行:1.线程由于等待notify()方法而处于阻塞状态。2.获得notify()方法的通知后,尝试获取锁,此时锁有可能是不可用的,因此会等待其它线程释放锁,而使线程阻塞。
3.一定要让“阿姨线程”先拿到锁,如果“学生线程”先拿到锁,“阿姨线程”会由于拿不到锁而被阻塞,直到“学生线程”执行到wait()方法;但在这之前已经调用了notify()方法了,而“阿姨线程”没有执行到wait()方法,错过了“学生线程”发来的信号。
使用显示锁协作
调用一个对象的wait()、notify()方法之前必须获得这个对象的锁,但是使用显示的锁时不能获取某个特定对象的锁,因此也就不能在显示锁的临界区内使用这些方法。显示锁为我们提供了另一种类似wait()和notify()方法的线程协作机制,使用起来与wait()和notify()方法完全相同,我们用这种方式来改写学生打饭的例子:
class Student implements Runnable {
public void run() {
CafeteriaLockTest.lock.lock();
try{
System.out.println("学生:取到了一个碗");
System.out.println("学生:阿姨帮忙盛饭");
CafeteriaLockTest.wan.signal();
CafeteriaLockTest.wan.await();
System.out.println("学生:吃饭");
} catch (InterruptedException e) { }
finally {
CafeteriaLockTest.lock.unlock();
}
}
}
class CafeteriaWorker implements Runnable {
public void run() {
CafeteriaLockTest.lock.lock();
try {
CafeteriaLockTest.wan.await();
System.out.println("阿姨:给学生盛饭");
CafeteriaLockTest.wan.signal();
}
catch (InterruptedException e) { }
finally {
CafeteriaLockTest.lock.unlock();
}
}
}
public class CafeteriaLockTest {
public static Lock lock = new ReentrantLock();
public static Condition wan;
public static void main(String[] args) throws InterruptedException {
wan = lock.newCondition();
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new CafeteriaWorker());
Thread.sleep(100);//等阿姨准备好
exec.execute(new Student());
exec.shutdown();
}
}
输出结果如下:
学生:取到了一个碗
学生:阿姨帮忙盛饭
阿姨:给学生盛饭
学生:吃饭
在代码中我们定义了一个重入锁的对象作为两个线程共用的锁,又调用lock.newCondition()方法获取一个Condition对象用来实现多线程协作,Condition的await()方法相当于Object的wait()方法,signal()方法相当于Object的notify()方法,Condition还有一个signalAll()方法相当于Object的notifyAll()方法。有个需要注意的地方就是,在调用Condition的await()方法时不要误用wait()方法。
总结
本节讲了如何让多个线程协作完成某项任务,其中wait()方法和之前讲过的Thread.sleep()方法类似,两者都使线程处于阻塞状态,但wait()方法要求调用之前必须获取对象的内置锁,sleep()方法调用时没有前置条件;另一个区别是wait()方法调用后会释放对象的锁,而sleep()方法不释放锁。线程间的协作未完待续。
公众号:今日说码。关注我的公众号,可查看连载文章。遇到不理解的问题,直接在公众号留言即可。
Java并发编程(八)线程间协作(上)的更多相关文章
- Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition
Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...
- 19、Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition
Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...
- Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)
Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...
- 【转】Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)
一.线程的状态 Java中线程中状态可分为五种:New(新建状态),Runnable(就绪状态),Running(运行状态),Blocked(阻塞状态),Dead(死亡状态). New:新建状态,当线 ...
- Java并发编程:线程控制
在上一篇文章中(Java并发编程:线程的基本状态)我们介绍了线程状态的 5 种基本状态以及线程的声明周期.这篇文章将深入讲解Java如何对线程进行状态控制,比如:如何将一个线程从一个状态转到另一个状态 ...
- Java并发编程:线程池的使用
Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
- Java并发编程:线程池的使用(转)
Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
- Java并发编程:线程池的使用(转载)
转载自:https://www.cnblogs.com/dolphin0520/p/3932921.html Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实 ...
- Java并发编程:线程池的使用(转载)
文章出处:http://www.cnblogs.com/dolphin0520/p/3932921.html Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实 ...
- [转]Java并发编程:线程池的使用
Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
随机推荐
- javascript 获取服务时间
用到了jquery的ajax方法,ajax自己写也可以. 具体用法 var setId = setInterval(function(){ var xhr = $.ajax({ type: 'HEAD ...
- SpringBoot 整合 Mybatis + Mysql——XML配置方式
一.介绍 SpringBoot有两种方法与数据库建立连接,一种是集成Mybatis,另一种用JdbcTemplate,本文主要讨论集成Mybatis方式. SpringBoot整合Mybatis也有两 ...
- 使用 npm 安装 Vue
使用 npm 安装 Vue 需要 node.js 就不多说了(从 nodejs.org 中下载 nodejs ) (1)安装 Vue,在 cmd 里直接输入: npm install -g cnpm ...
- django源码研究
研究django源码一年,从启动django开始
- javascript通用代码合集
1.逐一绑定操作到window.onload上 //func:新函数 function addLoadEvent(func){ //把现有的window.onload事件处理函数的值存入变量oldon ...
- spring cloud zuul 配置
参考:http://www.ityouknow.com/springcloud/2017/06/01/gateway-service-zuul.html spring boot版本:2.0.3.REL ...
- CentOS7 安装 JIRA 7.2.x 教程:下载、安装、汉化、破解
1.先看视频,参考着能装出个试用版来,不同的地方后面再做说明.JIRA 安装视频(Linux) http://www.confluence.cn/pages/viewpage.action?pageI ...
- ORM------多表操作
上面介绍了单表操作 下面就好比我们的sql语句这只能满足于我们的一些简单的操作不能适应我们更多的需要 所以我们需要用到更多的需求来进行我们的关系的建立以及查找 其实ORM语句就对应着我们的sql语句 ...
- Mysql常用函数汇总-经典实用
以下是对mysql中的常用函数进行了汇总介绍.需要的朋友可以过来参考下. 一.数学函数ABS(x) 返回x的绝对值BIN(x) 返回x的二进制(OCT返回八进制,HEX返回十六进制)CEILING(x ...
- linux 三大利器 grep sed awk 正则表达式
正则表达式目标 正则表达式单字符: 特定字符 范围字符:单个字符[ ] :代表查找单个字符,括号内为字符范围 数字字符:[0-9],[259] 查找 0~9 和 2.5 .9 中的任意一个字符 小写字 ...