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并发编程基础 并发 在计算机科学中,并发是指将一个程序,算法划分为若干个逻辑组成部分,这些部分可以以任何顺序进行执行,但与最终顺序执行的结果一致.并发可以在多核操作系统上显著的提高程序运行速度 ...
随机推荐
- 3D结构光
3D结构光 3D结构光的整个系统包含结构光投影设备.摄像机.图像采集和处理系统.其过程就是投影设备发射光线到被测物体上,摄像机拍摄在被测物体上形成的三维光图形,拍摄图像经采集处理系统处理后获得被测物体 ...
- AlexeyAB DarkNet YOLOv3框架解析与应用实践(二)
AlexeyAB DarkNet YOLOv3框架解析与应用实践(二) 版本3有什么新功能? YOLOv3使用了一些技巧来改进训练和提高性能,包括:多尺度预测.更好的主干分类器等等.全部细节都在我们的 ...
- YOLOvi(i=1,2,3,4)系列
YOLOvi(i=1,2,3,4)系列 YOLOv4论文链接:https://arxiv.org/pdf/2004.10934.pdf YOLOv4源码链接:https://github.com/Al ...
- Kaggle上的犬种识别(ImageNet Dogs)
Kaggle上的犬种识别(ImageNet Dogs) Dog Breed Identification (ImageNet Dogs) on Kaggle 在本节中,将解决在Kaggle竞赛中的犬种 ...
- python小知识,字典
知识融合在代码中 """ create:2020年12月20日 功能:字典的部分使用方法 """ #空字典 dic={} print(&qu ...
- 《四大点,搞懂Redis到底快在哪里?》
一.开发语言 二.纯内存访问 三.单线程 四.非阻塞多路I/O复用机制 前言 Redis是一种基于键值对(Key-Value)的NoSQL数据库 ,Redis的Value可以由String,hash, ...
- Web端在线实时聊天,基于WebSocket(前后端分离)
这是一个简易的Demo,已经实现了基础的功能 之前一直想实现一个实时聊天的系统,一直没有去实践他.有一天吃饭的时候扫码点菜,几个人点菜能够实时更新,当时就在想,这应该是同一种技术. 刚好前段时间项目上 ...
- ABP Framework 研习社经验总结(6.28-7.2)
ABP Framework 研习社经验总结(6.28-7.2) 研习社初衷 在翻译 <实现领域驱动设计>-- 基于 ABP Framework 实现领域驱动设计实用指南 时,因为DDD理论 ...
- C#调用JAVA(二)调用方法
上期我们创建了jar包并放到了unity中,那么我们继续 如果您还没有看上一期请先看上一期,这是链接 C#调用JAVA(一)制作jar包 - 执著GodShadow - 博客园 (cnblogs.co ...
- Linux中系统时间同步ntpdate简介
Linux服务器运行久时,系统时间就会存在一定的误差,一般情况下可以使用date命令进行时间设置,但在做数据库集群分片等操作时对多台机器的时间差是有要求的,此时就需要使用ntpdate进行时间同步.所 ...