对于多线程之间的共享受限资源,我们是通过锁(互斥)的方式来进行保护的,从而避免发生受限资源被多个线程同时访问的问题。那么线程之间既然有互斥,那么也会有协作。线程之间的协作也是必不可少的,比如 盖个商场这一个任务,线程A打地基,线程B该楼,线程C装修。在线程A打地基的时候,线程B可以准备必要的盖楼材料,混凝土啊,准备塔吊之类的,但是只有在线程A地基完成之后,线程B才能正式的开始在地基的基础上向上盖楼。这就牵扯到线程间的协作问题。

在所有类的最顶部的父类Object对象中,有几个方法就是用于线程间协作的,notify()、notifyAll()、wait()、wait(long)和 wait(long,int),这些方法都是final的。
Wait方法,调用wait方法的时机,是当前线程需要某个资源/条件,不过该资源/条件不是自己能控制的,所以就可以调用wait()方法,来让该线程挂起来,来等待需要的资源/条件发生改变。

调用wait方法时,该线程会被notify/nofityAll方法唤醒,或者挂起一段时间后(调用wait(long)),再自己主动醒来。

而某个线程使用完某项资源后,在解除锁之前,决定先通知其他在等待这个资源的线程,我用好了,马上要释放资源了,你们准备好来访问该资源来干活吧。这个时候,就需要调用notify(随机通知一个线程)或notifyAll(通知所有等待这把锁的线程)。

wait()和notify()系列方法,都要在同步控制方法/同步控制块里才能调用。(就是使用 synchronized或 Lock对象加锁的代码 ),否则就会抛出 IllegalMonitorStateException异常。

举个简单的例子来描述这个过程。线程A,B,C,D,E都要执行打印任务,因为打印机只有一台,所以打印机代码添加 了 synchronized ,线程A先得到了这个资源,所以打印机被加锁了,A在打印自己的东西,没人能给它抢打印机了,线程B一看,自己也用不了打印机了,那就休息一会吧,调用wait(500)方法,计划休息500毫秒之后,再看看打印机是否空闲。线程C和线程D一看这情况,我也休息吧。就调用wait()对象,线程C和D会一直休息,直到线程A调用notify/notifyAll方法。线程E一看,自己也没法用打印机,那就调用sleep(600),让自己也休息600毫秒再醒来看看吧。

当线程A用完打印机了,准备离开这块地方了(释放锁)之前,准备喊一嗓子通知其他线程。它可以调用notify()这个方法,会随机的通知到C,D线程中的一个,也可以调用notifyAll()方法,这个时候C,D线程都会被通知到。至于B,E线程,都是自己给自己设置了一个休息时间,所以到时间了自己醒来看看情况。

注意这个协助的过程,是仅限于等待打印机这个资源的线程。(也就是说,都使用了打印机这把锁,或者等待这把锁),至于那些执行扫地啊,泡茶啊之类的任务的线程,和打印机资源没关系的线程,它们是不在这个协作范围的。

(当然,这个例子中要注意的是,实际上,B,C,D都需要在某个同步代码块里才能调用wait)。

那么wait()和sleep()什么区别呢?使用wait()时,是把自己的锁释放了,而sleep()则是不释放锁的。所以如果该使用wait时,使用了sleep()时,那就麻烦了,因为你的锁没释放,你休息了,自己不使用资源了,也不释放锁,别人也无法使用这个资源。

看一个demo来验证这个问题。
先假设一辆车外壳的喷漆过程,这个过程是先要抛光(buff),然后再涂蜡(wax),然后再抛光,然后再涂蜡。这种过程要循环多次。

那么我们的demo就模拟一辆车,然后开启两个线程,一个执行抛光(Buffed)任务,另一个执行涂蜡(WaxOn)任务,明显的,这两个任务是需要相互协作的,因为不可能同时执行。并且是先抛光,后涂蜡,再抛光,再涂蜡,这个依次循环的过程。

代码地址:src\thread_runnable\WaxAndBuff.java

 class Car{
private boolean waxOn = false;
//涂蜡操作
public synchronized void waxed(){
waxOn = true; //已经涂蜡了,下一步可以 抛光了
notifyAll();
}
//抛光操作
public synchronized void buffing() {
waxOn = false;
notifyAll();
}
public synchronized void waitForWaxing() throws InterruptedException{
while(waxOn == false) {
wait();
}
}
public synchronized void waitForBuffing() throws InterruptedException{
while (waxOn == true){
wait();
}
}
}//end of "class Car" //执行 涂蜡 任务
class WaxOn implements Runnable{
private Car mCar; public WaxOn(Car mCar) {
this.mCar = mCar;
} @Override
public void run() {
// TODO Auto-generated method stub
try {
while (!Thread.interrupted()){
System.out.println("WaxOn, 开始涂蜡 " + Thread.currentThread().getName());
TimeUnit.MILLISECONDS.sleep(200); //模拟 涂蜡需要的 耗时
mCar.waxed();
mCar.waitForBuffing();
}
} catch (InterruptedException e) {
// TODO: handle exception
System.out.println("WaxOn, exiting via interrupt " + Thread.currentThread().getName());
}
System.out.println("WaxOn, ending of task " + Thread.currentThread().getName());
} }//end of "class WaxOn" //执行 抛光
class Buffed implements Runnable{
private Car mCar;
public Buffed(Car mCar) {
this.mCar = mCar;
}
@Override
public void run() {
try {
while(!Thread.interrupted()){
mCar.waitForWaxing();
System.out.println("Buffed, 开始抛光 " + Thread.currentThread().getName());
TimeUnit.MILLISECONDS.sleep(300); //模拟抛光需要的耗时
mCar.buffing();
}
} catch (InterruptedException e) {
System.out.println("Buffed, exiting via interrupt " + Thread.currentThread().getName());
}
System.out.println("Buffed, ending of task " + Thread.currentThread().getName());
}
} public class WaxAndBuff {
public static void main(String[] args) throws InterruptedException{
// TODO Auto-generated method stub
Car car = new Car();
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new Buffed(car));
exec.execute(new WaxOn(car));
TimeUnit.SECONDS.sleep(3); //main 线程sleep几秒钟
exec.shutdownNow();// interrupt,中断所有的任务 }
}

输出结果:

析代码如下,在Car对象中,使用一个boolean值 waxOn来标识是否已经涂蜡了,waxed方法(涂蜡)和抛光buffing方法(抛光)来模拟对应的动作,并且改变waxOn的状态,当他们完成各自的操作后,调用nofityAll()来通知等待这把锁的其他线程。而对于某一个线程,在执行具体的操作之前,调用 waitForWaxing方法或waitForBuffing方法来查看是否满足执行操作的条件。(不要忘记了我们操作Car的实际操作步骤,先抛光,再涂蜡,然后再抛光,这是一个循环的过程)。在两个等待方法中,如果发现不满足执行下一步操作的条件,那么就调用wait()方法来挂起自己,等待状态被改变。

然后我们分别开启两个线程来执行这个操作Car外壳的过程,WaxOn(涂蜡)任务和 Buffed(抛光)任务,在WaxOn线程中,先操作waxed()操作,,然后调用 waitForBuffing等待,此时该线程被挂起,Buffed线程启动,先调用 waitForWaxing来查看是否满足抛光的条件,不满足则挂起,满足则执行buffing操作。

然后依次循环执行三秒钟之后,我们调用 shutdownNow()立刻关闭所有线程。

有一点需要注意的地方,waitForWaxing和waitForBuffing中,查询等待条件使用的是while而不是if,这是合理的。如果一个锁被多个线程所使用,或者 该线程等待的条件有多个,那么使用while每次醒来时,都进行判断,自己是否可以运行了,这是最合理的方式。

这几篇java多线程文章的demo代码下载地址 http://download.csdn.net/detail/yaowen369/9786452

-------
作者:www.yaoxiaowen.com
github: https://github.com/yaowen369

java多线程(七)-线程之间的 协作的更多相关文章

  1. Java多线程编程-线程之间的通信

    转载自:这里 学习了基础的线程知识 看到了 线程之间的通信 线程之间有哪些通信方式呢? 1.同步 这里讲的同步是指多个线程通过synchronized关键字这种方式来实现线程间的通信. public ...

  2. JAVA多线程之线程间的通信方式

    (转发) 收藏 记 周日,北京的天阳光明媚,9月,北京的秋格外肃穆透彻,望望窗外的湛蓝的天,心似透过栏杆,沐浴在这透亮清澈的蓝天里,那朵朵白云如同一朵棉絮,心意畅想....思绪外扬, 鱼和熊掌不可兼得 ...

  3. Java多线程——线程之间的协作

    Java多线程——线程之间的协作 摘要:本文主要学习多线程之间是如何协作的,以及如何使用wait()方法与notify()/notifyAll()方法. 部分内容来自以下博客: https://www ...

  4. Java多线程之线程协作

    Java多线程之线程协作 一.前言 上一节提到,如果有一个线程正在运行synchronized 方法,那么其他线程就无法再运行这个方法了.这就是简单的互斥处理. 假如我们现在想执行更加精确的控制,而不 ...

  5. java并发系列(二)-----线程之间的协作(wait、notify、join、CountDownLatch、CyclicBarrier)

    在java中,线程之间的切换是由操作系统说了算的,操作系统会给每个线程分配一个时间片,在时间片到期之后,线程让出cpu资源,由其他线程一起抢夺,那么如果开发想自己去在一定程度上(因为没办法100%控制 ...

  6. Java并发编程,互斥同步和线程之间的协作

    互斥同步和线程之间的协作 互斥同步 Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLo ...

  7. java并发之线程间通信协作

    在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界 ...

  8. Java多线程之线程的生命周期

    Java多线程之线程的生命周期 一.前言 当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态.在线程的生命周期中,它要经过新建(New).就绪(Runnable).运行(R ...

  9. java多线程:线程间通信——生产者消费者模型

    一.背景 && 定义 多线程环境下,只要有并发问题,就要保证数据的安全性,一般指的是通过 synchronized 来进行同步. 另一个问题是,多个线程之间如何协作呢? 我们看一个仓库 ...

随机推荐

  1. CSS3 box-shadow 内外阴影效果

    说明 box-shadow 属性可以给元素边框周围添加一个或者多个阴影效果.定义多个阴影,使用逗号分隔. 语法 box-shadow: none | [inset? && [<o ...

  2. 学习python登录demo

    要求编写登录接口 : 1. 输入用户名和密码 2.认证成功后显示欢迎信息 3.用户名输错,提示用户不存在,重新输入(5次错误,提示尝试次数过多,退出程序) 4.用户名正确,密码错误,提示密码错误,重新 ...

  3. springmvc关于前台日期作为实体类对象参数类型转换错误

    页面报错: 后台错误: Field error in object 'user' on field 'birthday': rejected value [2013-06-24]; codes [ty ...

  4. HDU4466 Triangle

    题意:给一个长为N的铁丝,问你有几种方法将其划分为若干段,使得每一段都能围成一个边长为整数的三角形,并且围成的三角形都相似 思路其实很明显,三角形的周长必定是N的约数,那么答案就是周长C能围城的三角形 ...

  5. django同时查询两张表的数据,合并检索对象返回

    原始需求: 1.一篇文章内容分N个版块,每篇文章的版块数量不同. 2.有个文章搜索功能,需要同时搜索标题和内容. 实现思路: 1.由于每篇文章的内容版块数量不同,因此将每个文章的标题和内容分开存入2张 ...

  6. Golang开发环境搭建(Notepad++、LiteIDE两种方式以及martini框架使用)

    本文介绍两种Golang的开发环境一种基于notepad++.还有一种基于liteide. 1.下载Golang语言的pkg:http://golangtc.com/download 直接点击安装,一 ...

  7. CS:APP3e 深入理解计算机系统_3e bomblab实验

    bomb.c /*************************************************************************** * Dr. Evil's Ins ...

  8. 理解Object.defineProperty()

    理解Object.defineProperty() Object.defineProperty() 方法直接在一个对象上定义一个新属性,或者修改一个已经存在的属性, 并返回这个对象. 基本语法:Obj ...

  9. NodeJs学习笔记(三)

    最近在用sails框架写移动后台,马上就过年了,打算总结一下.   1.资源      node官方网站: www.nodejs.org      sails官方网站:www.sailsjs.org ...

  10. margin相关

    1.图片与文字对齐问题 如果图片和文字差不多大时,使用兼容性强的margin负值方法. 方法:img{margin:0 3px -3px 0;} 2.div嵌套后margin出现失效(转移)问题 原因 ...