多线程并发执行时,不同的线程执行的内容之间可能存在一些依赖关系,比如线程一执行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并发编程(八)线程间协作(上)的更多相关文章

  1. Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition

    Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...

  2. 19、Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition

    Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...

  3. Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)

    Java并发编程系列: Java 并发编程:核心理论 Java并发编程:Synchronized及其实现原理 Java并发编程:Synchronized底层优化(轻量级锁.偏向锁) Java 并发编程 ...

  4. 【转】Java 并发编程:线程间的协作(wait/notify/sleep/yield/join)

    一.线程的状态 Java中线程中状态可分为五种:New(新建状态),Runnable(就绪状态),Running(运行状态),Blocked(阻塞状态),Dead(死亡状态). New:新建状态,当线 ...

  5. Java并发编程:线程控制

    在上一篇文章中(Java并发编程:线程的基本状态)我们介绍了线程状态的 5 种基本状态以及线程的声明周期.这篇文章将深入讲解Java如何对线程进行状态控制,比如:如何将一个线程从一个状态转到另一个状态 ...

  6. Java并发编程:线程池的使用

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

  7. Java并发编程:线程池的使用(转)

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

  8. Java并发编程:线程池的使用(转载)

    转载自:https://www.cnblogs.com/dolphin0520/p/3932921.html Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实 ...

  9. Java并发编程:线程池的使用(转载)

    文章出处:http://www.cnblogs.com/dolphin0520/p/3932921.html Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实 ...

  10. [转]Java并发编程:线程池的使用

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

随机推荐

  1. PC端-上传头像并裁剪

    界面一: <link href="../theme/js/layui.layim/src/css/layui.css" rel="stylesheet"/ ...

  2. Java Basis

    java中.java源文件放在src文件夹下,.class文件放在bin文件夹下. java代码区域,以及控制台区域字体大小更改.Java->Java Editor Text Font      ...

  3. php自动获取上一个月的起始时间

    1.借鉴评论的方法[20170309 edit] function get_month_start_end($timestamp) { !empty($timestamp) OR $timestamp ...

  4. C语言中关键词static的用法与作用域

    一.面向过程设计中的static 转载:http://blog.csdn.net/celerylxq/article/details/6160499 1.静态全局变量 在全局变量前,加上关键字stat ...

  5. 算法day02

    算法动态演示网站 数组是有长度限制的,有类型限制 CPU计算 每次是2的32位的,32根线,支持最大是4G 数组:线性表 列表:动态表链表:存储的位置不是连续的 insert 时间复杂度是 Onapp ...

  6. 安卓性能优化之清除Handler的Message和Runnable

    安卓性能优化之清除Handler的Message和Runnable Handler是由系统所提供的一种异步消息处理的常用方式,一般情况下不会发生内存泄露. 但既然是调优,当在A_Activity中使用 ...

  7. Python 发送邮件案例

    文件形式的邮件 #!/usr/bin/env python #coding: utf-8 import smtplib from email.mime.text import MIMEText fro ...

  8. oracle decode函数和 sign函数

    流程控制函数 DECODE decode()函数简介: 主要作用: 将查询结果翻译成其他值(即以其他形式表现出来,以下举例说明): 使用方法: Select decode(columnname,值1, ...

  9. web application 访问控制

    http://secappdev.org/handouts/2012/Jim%20Manico%20%26%20%20Eoin%20Keary/Final%20-%20Access%20Control ...

  10. 内置的HTTP服务器【Modern PHP】

    目录 启动服务器 配置服务器 路由器脚本 判断是否为内置的服务器 PHP5.4.0起,PHP内置了Web服务器.对本地开发是个极好的工具,便捷,无需安装WAMP.XAMP或大新那个web服务器,就能在 ...