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并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...
随机推荐
- 【转载】python实例手册
今天写爬虫的时候遇到了问题,在网上不停地查找资料,居然碰到两篇好文章: 1.python实例手册 作者:没头脑的土豆 另一篇在这:shell实例手册 python实例手册 #encoding:ut ...
- 【javascript】javascript设计模式之单例模式
单例模式: 定义:单例模式之所以这么叫,是因为它限制一个类只能有一个实例化对象. 实现方法:判断实例是否存在,如果存在则直接返回,如果不存在就创建了再返回.(确保一个类只有一个实例对象) 特点: 命名 ...
- MyEclipse 2017/2018 安装与破解 图文教程
SSM 框架-02-MyEclipse 2017/2018 安装与破解 现在在学J2EE,然后使用的工具就是 MyEclipse,现在就抛弃 Eclipse 了,我就不多说它俩的区别了,但是 MyEc ...
- 使用Netty3或Netty4发布Http协议服务
现在是2018年1月11日18:12分,已经是下班时间了,小Alan今天给大家简单的介绍一下Netty,让大家以后在使用到Netty的时候能够有一定的了解和基础,这样深入学习Netty以及以后灵活应用 ...
- weixin设置菜单
https://jingyan.baidu.com/article/925f8cb8dfcf11c0dce05661.html
- laravel入门-01
创建laravel应用 laravel new app_name 使用 PHP 内置 web server 驱动我们的网站 cd xxx/public php -S localhost:port 查看 ...
- Excel开发之旅(三)——添加侧边工具栏
1. 添加自定义用户控件:选择项目添加新建项用户控件.修改文件名,点击添加即可. 2. 重复步骤1,再添加3个自定义控件,接下来我们在自定义用户控件上面添加一些工具箱组件 3. 由于我们添加的是侧边工 ...
- github发布静态页面
github发布静态页面:https://wangc1993.github.io/2019/01/07/2/
- Windows Server、 Windows 区别
今天脑补了普通Windows 操作系统与Windows Server区别,感觉清楚了很多. Microsoft WindowsServer,是美国微软公司研制的一套操作体系,它面世于1985年,起先仅 ...
- January 01 2017 Week 1st Sunday
This is a new year. A new beginning. And things will change. 新一年,新开始,新气象. Hey Hey Hey. I can see my ...