Java Thread系列(四)线程通信

一、传统通信

public static void main(String[] args) {
//volatile实现两个线程间数据可见性
private volatile static List list = new ArrayList(); Thread t1 = new Thread(new Runnable() { // (1)
public void run() {
try {
for(int i = 0; i <10; i++){
list.add(i);
System.out.println(Thread.currentThread().getName() + "线程添加第" + (i + 1) + "个元素..");
Thread.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1"); Thread t2 = new Thread(new Runnable() { // (2)
public void run() {
while(true){
if(list.size() == 5){
//do something
throw new RuntimeException(Thread.currentThread().getName() +
"线程接到通知 size = " + list.size() + " 线程停止..");
}
}
}
}, "t2"); t1.start();
t2.start();
}
  1. t1 线程不断将生产的数据放入 list 集合中

  2. t2 线程开启 while 循环监听 t1 线程,虽然可以实现 list.size()==5 时实时通知 t2 线程,但太浪费性能,考虑用 await/notify 提高性能,程序执行结果如下:

t1线程添加第1个元素..
t1线程添加第2个元素..
t1线程添加第3个元素..
t1线程添加第4个元素..
t1线程添加第5个元素..
Exception in thread "t2" java.lang.RuntimeException: t2线程接到通知 size = 5 线程停止..
at com.github.binarylei.thread._2_1conn.ListAdvice1$2.run(ListAdvice1.java:35)
at java.lang.Thread.run(Thread.java:745)
t1线程添加第6个元素..
t1线程添加第7个元素..
t1线程添加第8个元素..
t1线程添加第9个元素..
t1线程添加第10个元素..

二、wait/notify 实现通信

/**
* 使用wait/notify方法实现线程单挑通信(注意这两个方法是Object类的方法)
* 1. wait和notity必须配合synchronized关键字使用
* 2. wait方法(关闭线程)释放锁,notify(唤醒线程)方法不释放锁
* 缺点:通知不实时,使用CountDownLatch实现实时通知
*/
public static void main(String[] args) {
private volatile static List list = new ArrayList();
final Object lock = new Object(); Thread t1 = new Thread(new Runnable() { // (1)
public void run() {
try {
synchronized (lock) {
System.out.println("t1启动..");
for(int i = 0; i <10; i++){
list.add(i);
System.out.println(Thread.currentThread().getName() + "线程添加第" + (i + 1) + "个元素..");
Thread.sleep(500);
if(list.size() == 5){
System.out.println("已经发出通知..");
lock.notify();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1"); Thread t2 = new Thread(new Runnable() { // (2)
public void run() {
synchronized (lock) {
System.out.println("t2启动..");
if(list.size() != 5){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//do something
throw new RuntimeException(Thread.currentThread().getName() +
"线程接到通知 size = " + list.size() + " 线程停止..");
}
}
}, "t2");
}
  1. t1 线程当 list.size()==5lock.notify() 唤醒 t2 线程,注意 wait/notify 必须配合 synchronized 使用

  2. t2 线程调用 lock.wait() 后处于一直阻塞状态,直到 t1 线程调用 lock.notify() 唤醒该线程,倘若没有线程唤醒 t2 线程,那么 t2 线程就一直处于阻塞状态。本例中若 t1 线程先启动,那么 t2 线程调用 lock.wait() 就永远阻塞无法执行。程序执行结果如下:。

t2启动..
t1启动..
t1线程添加第1个元素..
t1线程添加第2个元素..
t1线程添加第3个元素..
t1线程添加第4个元素..
t1线程添加第5个元素..
已经发出通知..
t1线程添加第6个元素..
t1线程添加第7个元素..
t1线程添加第8个元素..
t1线程添加第9个元素..
t1线程添加第10个元素..
Exception in thread "t2" java.lang.RuntimeException: t2线程接到通知 size = 10 线程停止..
at com.github.binarylei.thread._2_1conn.ListAdd2$2.run(ListAdd2.java:51)
at java.lang.Thread.run(Thread.java:745)
  1. 由于 t1 线程 lock.notify() 后不会释放锁,t2 线程虽然被唤醒但不能获取锁,所以通知就不那么实时,只有等 t1 线程执行完成释放锁后 t2 线程才能获得锁执行相应操作,解决方案:使用 CountDownLatch

三、CountDownLatch 实现实时通信


public static void main(String[] args) {
private volatile static List list = new ArrayList();
final CountDownLatch countDownLatch = new CountDownLatch(1); // (1) Thread t1 = new Thread(new Runnable() {
public void run() {
try {
System.out.println("t1启动..");
for(int i = 0; i <10; i++){
list.add(i);
System.out.println(Thread.currentThread().getName() + "线程添加第" + (i + 1) + "个元素..");
Thread.sleep(500);
if(list.size() == 5){
System.out.println("已经发出通知..");
countDownLatch.countDown(); // (2)
}
}
} catch (InterruptedException e) {
e.printStackTrace();
} }
}, "t1"); Thread t2 = new Thread(new Runnable() {
public void run() {
System.out.println("t2启动..");
if(list.size() != 5){
try {
countDownLatch.await(); // (3)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//do something
throw new RuntimeException(Thread.currentThread().getName() +
"线程接到通知 size = " + list.size() + " 线程停止..");
}
}, "t2"); t1.start();
t2.start();
}
  1. CountDownLatch 同步工具类,允许一个或多个线程一直等待,直到其他线程的操作执行完后再执行,参数 1 表示需要等待的线程数量,具体来说就是参数为几就必须调用几次 countDownLatch.countDown()

  2. countDownLatch.countDown() 唤醒线程

  3. countDownLatch.await() 阻塞线程,程序执行结果如下:

t1启动..
t1线程添加第1个元素..
t2启动..
t1线程添加第2个元素..
t1线程添加第3个元素..
t1线程添加第4个元素..
t1线程添加第5个元素..
已经发出通知..
Exception in thread "t2" java.lang.RuntimeException: t2线程接到通知 size = 5 线程停止..
t1线程添加第6个元素..
at com.github.binarylei.thread._2_1conn.ListAdd3$2.run(ListAdd3.java:47)
at java.lang.Thread.run(Thread.java:745)
t1线程添加第7个元素..
t1线程添加第8个元素..
t1线程添加第9个元素..
t1线程添加第10个元素..

四、ThreadLocal

ThreadLocal 是线程局部变量,是一种多线程间并发访问变量的无锁解决方案。

ThreadLocal 和 synchronized 比较?

  1. 与 synchronized 等加锁的方式不同,ThreadLocal 完全不提供锁,而使用以空间换时间的手段,为每个线程提供变量的独立副本,以保障线程安全。

  2. 从性能上说,ThreadLocal 不具有绝对的优势,在并发不是很高的时候,加锁的性能会更好,但作为一套无锁的解决方案,在高并发量或者竞争激烈的场景,使用 ThreadLocal 可以在一定程度上减少锁竞争。

public static void main(String[] args) throws InterruptedException {
final ThreadLocal<String> th = new ThreadLocal<String>(); Thread t1 = new Thread(new Runnable() {
public void run() {
th.set("张三");
System.out.println(th.get()); // => "张三"
}
}, "t1"); Thread t2 = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(1000);
th.set("李四");
System.out.println(th.get()); // => "李四"
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t2"); t1.start(); //t1:张三
t2.start(); //t2:李四
}

五、自定义同步类窗口-Queue

Java 提供了一些同步类容器,它们是 线程安全 的,如 Vector、HashTable 等。这些同步类容器是由 Collections.synchronizedMap 等工厂方法去创建实现的,底层使用 synchronized 关键字,每次只有一个线程访问容器。下面实现一个自己的同步类窗口。

import java.util.LinkedList;

public class MyQueue {
private LinkedList list = new LinkedList();
private int max = 5;
private int min = 1;
private Object lock = new Object(); public void put(Object obj) { // (1)
synchronized (lock) {
while (list.size() == max) {
try {
lock.wait();
} catch (InterruptedException e) {
;
}
}
list.add(obj);
lock.notify();
System.out.println("put元素:" + obj);
}
} public Object take() { // (2)
Object obj;
synchronized (lock) {
while (list.size() == min) {
try {
lock.wait();
} catch (InterruptedException e) {
;
}
}
obj = list.removeFirst();
lock.notify();
System.out.println("take元素:" + obj);
}
return obj;
}
}

测试

public static void main(String[] args) {
final MyQueue myQueue = new MyQueue();
myQueue.put("a");
myQueue.put("b");
myQueue.put("c");
myQueue.put("d");
myQueue.put("e"); new Thread(new Runnable() {
@Override
public void run() {
myQueue.put("f");
myQueue.put("g");
myQueue.put("h");
myQueue.put("i");
}
}).start(); new Thread(new Runnable() {
@Override
public void run() {
myQueue.take();
myQueue.take();
myQueue.take();
}
}).start();
}

每天用心记录一点点。内容也许不重要,但习惯很重要!

Java Thread系列(四)线程通信的更多相关文章

  1. Java Thread系列(二)线程状态

    Java Thread系列(二)线程状态 一.线程的五种状态 新建状态(New):新创建了一个线程对象,尚未启动. 就绪状态(Runnable):也叫可运行状态.线程对象创建后,其他线程调用了该对象的 ...

  2. Java多线程系列--“JUC线程池”05之 线程池原理(四)

    概要 本章介绍线程池的拒绝策略.内容包括:拒绝策略介绍拒绝策略对比和示例 转载请注明出处:http://www.cnblogs.com/skywang12345/p/3512947.html 拒绝策略 ...

  3. Java Thread系列(三)线程安全

    Java Thread系列(三)线程安全 一.什么是线程安全 线程安全概念:当多个线程访问某一个类(对象或方法)时,这个类始终都能表现出正确的行为,那么这个类(对象或方法)就是线程安全的. 线程安全来 ...

  4. Java Thread系列(一)线程创建

    Java Thread系列(一)线程创建 Java 中创建线程主要有三种方式:继承 Thread.实现 Runnable 接口.使用 ExecutorService.Callable.Future 实 ...

  5. Java多线程系列--“JUC线程池”06之 Callable和Future

    概要 本章介绍线程池中的Callable和Future.Callable 和 Future 简介示例和源码分析(基于JDK1.7.0_40) 转载请注明出处:http://www.cnblogs.co ...

  6. Java Thread系列(十)Future 模式

    Java Thread系列(十)Future 模式 Future 模式适合在处理很耗时的业务逻辑时进行使用,可以有效的减少系统的响应时间,提高系统的吞吐量. 一.Future 模式核心思想 如下的请求 ...

  7. Java多线程系列--“JUC线程池”02之 线程池原理(一)

    概要 在上一章"Java多线程系列--“JUC线程池”01之 线程池架构"中,我们了解了线程池的架构.线程池的实现类是ThreadPoolExecutor类.本章,我们通过分析Th ...

  8. Java多线程系列--“JUC线程池”03之 线程池原理(二)

    概要 在前面一章"Java多线程系列--“JUC线程池”02之 线程池原理(一)"中介绍了线程池的数据结构,本章会通过分析线程池的源码,对线程池进行说明.内容包括:线程池示例参考代 ...

  9. Java Thread系列(五)synchronized

    Java Thread系列(五)synchronized synchronized锁重入 关键字 synchronized 拥有锁重入的功能,也就是在使用 synchronized 时,当线程等到一个 ...

随机推荐

  1. FastAdmin 升级后出现 is already in use

    FastAdmin 升级后出现 is already in use 升级 FastAdmin 改进很多,但全新安装出现以下错误 Cannot use app\common\library\Menu a ...

  2. php 取数组最后一个元素

    可以用end()函数取出数组的最后一个元素, $rList = array( array('id'=>1,'txt'=>'a'), array('id'=>2,'txt'=>' ...

  3. 数据抽取 CDC

    什么是数据抽取 数据抽取是指从源数据源系统抽取目的数据源系统需要的数据.实际应用中,数据源较多采用的是关系数据库. [编辑] 数据抽取的方式 (一) 全量抽取 全量抽取类似于数据迁移或数据复制,它将数 ...

  4. sysbench 0.5使用手册

    注意:本文刚开始只介绍了sysbench 0.5之前的版本,在了解了sysbench 0.5之后进行了补充,大部分测试和参数都是一样的,只是sysbench 0.5 在测试数据库方面更加全面丰富. 关 ...

  5. Oracle约束详解

    一 约束的定义 约束是强加在表上的规则或条件.确保数据库满足业务规则.保证数据的完整性.当对表进行DML或DDL操作时,如果此操作会造成表中的数据违反约束条件或规则的话,系统就会拒绝执行这个操作.约束 ...

  6. jython研究笔记

    jython目前只支持python2,不支持python3. python中使用第三方包的话需要重新设置lib的地址. public void getHtmlByTxt(String path) { ...

  7. Boost C++ 库 中文教程(全)

    Boost C++ 库 目录 第 1 章 简介 第 2 章 智能指针 第 3 章 函数对象 第 4 章 事件处理 第 5 章 字符串处理 第 6 章 多线程 第 7 章 异步输入输出 第 8 章 进程 ...

  8. ubuntu安装了mysql 但是编译报错 mysql.h: No such file or directory

    在Ubuntu体系中,已经安装了mysql,即应用sudo apt-get install mysql-server mysql-client 但是用C编译mysql数据库时,报错fatal erro ...

  9. 一、jdk工具之jps(JVM Process Status Tools)命令使用

    目录 一.jdk工具之jps(JVM Process Status Tools)命令使用 二.jdk命令之javah命令(C Header and Stub File Generator) 三.jdk ...

  10. LAMP的安装和注意事项

    LAMP--Linux+Apache(httpd)+MySQL+PHP,是常用的web服务器架构,下面接受编译安装的过程,以及出现的错误. 注意事项: 1. 扩展epel源:参照:http://www ...