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并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
随机推荐
- AndroidStudio中logcat不输出信息
2017年11月27日,记住这个日子.今天第一次感觉到被批评了,由于自己技术知识储备不足导致今天的外出工作等于浪费时间.正式因为这个logcat不输出信息的问题,前几回不输出信息了我就从新启动了开发工 ...
- OkHttp3源码详解(三) 拦截器
1.构造Demo 首先构造一个简单的异步网络访问Demo: OkHttpClient client = new OkHttpClient(); Request request = new Reques ...
- 辅助判卷程序的一些小bug
首先谈一下,double类型 之前查过一些资料,double类型做==(相等)判断时候,会出现一些错误,及61.95与61.95不相等 对main函数中的部分加以改正,下面的answer为string ...
- PHP后台处理jQuery Ajax跨域请求问题 — xx was not called解决办法
// 前台代码 $.ajax({ url: 'http://www.ushark.net/home/save_trial_apply', dataType: 'jsonp', processData: ...
- TPS和事务响应时间的关系、计算公式 (转)
例子:一个高速路有10个入口,每个入口每秒钟只能进1辆车1.请问1秒钟最多能进几辆车? TPS=102.每辆车需要多长时间进行响应? reponse time = 13.改成20辆车,每秒能进 ...
- xcopy-参数详解
XCOPY——目录复制命令 1.功能:复制指定的目录和目录下的所有文件连同目录结构. 2.类型:外部命令 3.格式:XCOPY [源盘:]〈源路径名〉[目标盘符:][目标路径名][/S][/V][/E ...
- Sql Server关于日期查询时,如果表中日期到具体某个时间
1.如果查询日期参数为'2017/02/21',而数据库表中的字段为'2017/02/21 12:34:16.963',则需要格式化一下日期才能查询出来,如下 select * from table ...
- Python 解决写入csv中间隔一行空行问题
转载解决写入csv中间隔一行空行问题 写入csv: with open(birth_weight_file,'w') as f: writer=csv.writer(f) writer.writero ...
- Python初学者第二十天 函数(3)-递归函数及练习题
20day 1.递归的返回值: 递归返回值 2.递归的特性: a.必须有一个明确的结束条件 b.每次进入更深一层递归时,问题规模相比上次递归都应有所减少 c.递归效率不高,递归层次过多会导致栈溢出 3 ...
- 使用事务和SqlBulkCopy批量插入数据
SqlBulkCopy是.NET Framework 2.0新增的类,位于命名空间System.Data.SqlClient下,主要提供把其他数据源的数据有效批量的加载到SQL Server表中的功能 ...