java并发编程基础——线程通信
线程通信
当线程在系统内运行时,程序通常无法准确的控制线程的轮换执行,但我们可以通过一些机制来保障线程的协调运行
一、传统的线程通信
传统的线程通信主要是通过Object类提供的wait(),notify(),notifyAll() 3个方法实现,这三个方法必须由同步监视器对象来调用
wait():导致当前线程等待,直到其他线程调用同步监视器的notify()方法或者notifyAll()方法来唤醒该线程。wait会释放当前同步监视器锁定
notify():唤醒此同步监视器上的等待的单个线程。如果所有线程都在此同步监视器上等待,则会选择唤醒其中一个线程
notifyAll():唤醒此同步监视器上所有等待的线程,只有当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的线程。
下面程序利用wait(),notify实现两个线程轮流对i自增
package threadtest; public class ThreadTest implements Runnable{ static int i = 0;
private int flag = 0 ;
public void incre() throws InterruptedException {
synchronized (this) {
if(flag == 0) {
flag = 1;
System.out.println(Thread.currentThread().getName());
i++;
this.notify();//唤醒其他线程
this.wait();//等待,并释放同步监视器锁定
}else if(flag == 1){
flag = 0;
System.out.println(Thread.currentThread().getName());
i++;
this.notify();//唤醒其他线程
this.wait();//等待,并释放同步监视器锁定
}else {
this.notifyAll();
}
}
} @Override
public void run() {
for(int j=0;j<100;j++) {
try {
incre();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
flag = 3;
try {
incre();
} catch (InterruptedException e) {
e.printStackTrace();
}
} public static void main(String[] args) throws InterruptedException {
ThreadTest tt = new ThreadTest();
Thread t1 = new Thread(tt);
Thread t2 = new Thread(tt);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
} }
结果:
Thread-0
Thread-1
Thread-0
...
Thread-1
Thread-0
Thread-1
200
二、使用Condition控制线程通信
如果不使用synchronized,而是使用Lock对象来保证同步,则系统中不存在隐士的同步监视器,也就能使用wait,notify,notifyAll来进行线程通信了。
当使用Lock对象来保证同步时,java提供了一个Condition类来保持协调。
Condition对象被绑定在一个Lock对象上,要获得,只要Lock对象的newCondition方法即可获得Condition对象。
Condition类包含如下3个方法
await():类似wait(),导致当前线程等待,直到其他线程调用该Condition的signal()方法或signalAll()方法来唤醒该线程。wait会释放当前同步监视器锁定
signal():唤醒在此Lock对象上等待的单个线程,如果该Lock上所有线程都在等待,则会选择唤醒其中一个线程
signalAll():唤醒在此Lock对象上等待的所有线程
下面程序利用lock,condition实现两个线程轮流对i自增
package threadtest; import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock; public class ThreadTest implements Runnable{ private final ReentrantLock lock = new ReentrantLock();
private final Condition cond = lock.newCondition();
static int i = 0;
private int flag = 0 ;
public void incre() throws InterruptedException {
try {
lock.lock();
if(flag == 0) {
flag = 1;
System.out.println(Thread.currentThread().getName());
i++;
cond.signal();
cond.await(); }else if(flag == 1){
flag = 0;
System.out.println(Thread.currentThread().getName());
i++;
cond.signal();
cond.await(); }else {
cond.signalAll();
}
} finally {
lock.unlock();
}
} @Override
public void run() {
for(int j=0;j<100;j++) {
try {
incre();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
flag = 3;
try {
incre();
} catch (InterruptedException e) {
e.printStackTrace();
}
} public static void main(String[] args) throws InterruptedException {
ThreadTest tt = new ThreadTest();
Thread t1 = new Thread(tt);
Thread t2 = new Thread(tt);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
} }
结果:
Thread-0
Thread-1
Thread-0
...
Thread-1
Thread-0
Thread-1
200
三、使用阻塞队列(BlockingQueue)控制线程通信
java5提供了BlockingQueue接口主要用于线程同步工具,它也是Queue的子接口。
BlockingQueue特点:当生产者试图向BlockingQueue中放入元素,如果该队列已满,则该线程被阻塞;当消费者线程试图从BlockingQueue中取元素时,如果该队列已空,则该线程被阻塞。
BlockingQueue接口中有两个方法支持阻塞:
put(E e):尝试把E元素放入BlockingQueue中,如果该队列已满,则阻塞该线程
take():尝试从BlockingQueue的头部取出元素,如果该队列已空,则阻塞该线程
BlockingQueue接口继承Queue接口,所以也可以用Queue接口中的方法:
在队尾部插入元素:add(E e)、offer(E e)、put(E e),当队列已满时,这三个方法会抛出异常、返回false、阻塞队列
在队列头部删除并返回删除的元素:remove()、poll、take(),当队列已空时,这个三个方法会抛出异常、返回false、阻塞队列
在队列头部取出但不删除元素:element()、peek(),当队列已空时,这两个方法分别抛出异常,返回false.
BlockingQueue接口常用实现类:
ArrayBlockingQueue:基于数组实现的BlockingQueue队列
LinkedBlockingQueue:基于链表实现的BlockingQueue队列
SynchronousQueue:同步队列。对该队列的存、取操作必须交替进行
BlockingQueue的小例子如下:
package threadtest; import java.util.concurrent.BlockingQueue; /**
* 生产着
* @author rdb
*
*/
public class Producer implements Runnable{ private BlockingQueue<Integer> bq ;
public Producer(BlockingQueue<Integer> bq) {
this.bq = bq;
} @Override
public void run() {
for(int i=0;i<30;i++) {
System.out.println(Thread.currentThread().getName() + "开始生产" + bq);
try {
bq.put(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "生产结束" + bq);
}
} } package threadtest; import java.util.concurrent.BlockingQueue; /**
* 消费者
* @author rdb
*
*/
public class Consumer implements Runnable{ private BlockingQueue<Integer> bq;
public Consumer(BlockingQueue<Integer> bq) {
this.bq = bq ;
}
@Override
public void run() {
while(true) {
System.out.println(Thread.currentThread().getName() + "开始消费" + bq);
try {
bq.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "消费完成" + bq);
}
} } package threadtest; import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock; public class ThreadTest { //启动三个生产者线程,一个消费者线程
public static void main(String[] args) {
BlockingQueue< Integer> bq = new ArrayBlockingQueue<>(1);
Producer p = new Producer(bq);
Consumer c = new Consumer(bq);
new Thread(p,"produce1").start();
new Thread(p,"produce2").start();
new Thread(p,"produce3").start();
new Thread(c,"consumer").start(); } }
结果:
produce2开始生产[]
consumer开始消费[]
produce3开始生产[]
produce1开始生产[]
produce2生产结束[1]
produce3生产结束[1]
produce2开始生产[1]
consumer消费完成[]
produce3开始生产[1]
consumer开始消费[1]
consumer消费完成[]
produce1生产结束[1]
consumer开始消费[1]
produce1开始生产[1]
produce2生产结束[1]
consumer消费完成[]
........
consumer消费完成[]
consumer开始消费[]
java并发编程基础——线程通信的更多相关文章
- Java并发编程基础-线程安全问题及JMM(volatile)
什么情况下应该使用多线程 : 线程出现的目的是什么?解决进程中多任务的实时性问题?其实简单来说,也就是解决“阻塞”的问题,阻塞的意思就是程序运行到某个函数或过程后等待某些事件发生而暂时停止 CPU 占 ...
- java并发编程基础——线程的创建
一.基础概念 1.进程和线程 进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程.(进程是资源分配的最小单位) 线程:同一类线程共享代码和数据 ...
- java并发编程基础——线程相关的类
线程相关类 java还为线程安全提供了一些工具类. 一.ThreadLocal类(Thread Local Variable) ThreadLocal类,是线程局部变量的意思.功用非常简单,就是为每一 ...
- java并发编程基础——线程池
线程池 由于启动一个线程要与操作系统交互,所以系统启动一个新的线程的成本是比较高的.在这种情况下,使用线程池可以很好的提升性能,特别是程序中涉及创建大量生命周期很短暂的线程时. 与数据库连接池类似,线 ...
- java并发编程基础——线程同步
线程同步 一.线程安全问题 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码.如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安 ...
- Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition
Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...
- Java并发编程基础
Java并发编程基础 1. 并发 1.1. 什么是并发? 并发是一种能并行运行多个程序或并行运行一个程序中多个部分的能力.如果程序中一个耗时的任务能以异步或并行的方式运行,那么整个程序的吞吐量和可交互 ...
- 19、Java并发编程:线程间协作的两种方式:wait、notify、notifyAll和Condition
Java并发编程:线程间协作的两种方式:wait.notify.notifyAll和Condition 在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作.比如说最经典的生产者-消费者 ...
- 并发-Java并发编程基础
Java并发编程基础 并发 在计算机科学中,并发是指将一个程序,算法划分为若干个逻辑组成部分,这些部分可以以任何顺序进行执行,但与最终顺序执行的结果一致.并发可以在多核操作系统上显著的提高程序运行速度 ...
随机推荐
- C ++基本输入/输出
C ++基本输入/输出 本文将学习如何使用cin对象从用户那里获取输入,并使用cout对象在示例的帮助下向用户显示输出. C ++输出 在C ++中,cout将格式化的输出发送到标准输出设备,例如屏幕 ...
- 前端 JS 之 AJAX 简介及使用
概述 AJAX 是一个缩写,它的全名是 Asynchronous JavaScript and XML,意思就是异步 JavaScript 和 XML,即用JavaScript执行异步网络请求. AJ ...
- WordPress安装篇(2):用宝塔面板在Windows上安装WordPress
上一篇文章介绍了如何使用PHPStudy工具在Windows Server环境安装WordPress,接下来介绍一款更加强大的部署WordPress的集成工具--宝塔面板.宝塔面板不仅提供免费版本,还 ...
- H5播放需要解密的m3u8音频文件
<audio ref="audio"></audio> import CryptoJS from "crypto-js"; import ...
- 【题解】将军令 Luogu P3942 (未完成)
历史/落在/赢家/之手 至少/我们/拥有/传说 谁说/败者/无法/不朽 拳头/只能/让人/低头 念头/却能/让人/抬头 抬头/去看/去爱/去追 你心中的梦 将军令 题目描述 又想起了四月. 如果不是省 ...
- mturoute 最大传输单元路由检测Host
mturoute检测mtu字符 下载地址:https://www.elifulkerson.com/projects/mturoute.php mturoute.exe ...
- 65.QT-UDP组播实现多人共享桌面(同时支持收发显示)
这里我们只是简单学习下通过udp组播如何共享桌面demo.帧率上面比较低,毕竟没有用推流,只是简单的将图片发送到组播地址,而加入组播地址的客户端去取数据显示而已. 主要是为了学习UDP知识而写的,真的 ...
- VBS脚本编程(10)——编写WMI脚本
WMI介绍 1.WMI是什么? WMI--Windows管理规范(Windows Management instrumentation). 是一项核心的Windows管理技术. 采用统一的.基于开放标 ...
- CSS 奇思妙想 | 全兼容的毛玻璃效果
通过本文,你能了解到 最基本的使用 CSS backdrop-filter 实现磨砂玻璃(毛玻璃)的效果 在至今不兼容 backdrop-filter 的 firefox 浏览器,如何利用一些技巧性的 ...
- Redis的主从数据一致性
我们学习了 AOF 和 RDB,如果 Redis 发生了宕机,它们可以分别通过回放日志和重新读入 RDB 文件的方式恢复数据,从而保证尽量少丢失数据,提升可靠性.不过,即使用了这两种方法,也依然存在服 ...