线程通信

当线程在系统内运行时,程序通常无法准确的控制线程的轮换执行,但我们可以通过一些机制来保障线程的协调运行

一、传统的线程通信

传统的线程通信主要是通过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并发编程基础——线程通信的更多相关文章

  1. Java并发编程基础-线程安全问题及JMM(volatile)

    什么情况下应该使用多线程 : 线程出现的目的是什么?解决进程中多任务的实时性问题?其实简单来说,也就是解决“阻塞”的问题,阻塞的意思就是程序运行到某个函数或过程后等待某些事件发生而暂时停止 CPU 占 ...

  2. java并发编程基础——线程的创建

    一.基础概念 1.进程和线程 进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程.(进程是资源分配的最小单位) 线程:同一类线程共享代码和数据 ...

  3. java并发编程基础——线程相关的类

    线程相关类 java还为线程安全提供了一些工具类. 一.ThreadLocal类(Thread Local Variable) ThreadLocal类,是线程局部变量的意思.功用非常简单,就是为每一 ...

  4. java并发编程基础——线程池

    线程池 由于启动一个线程要与操作系统交互,所以系统启动一个新的线程的成本是比较高的.在这种情况下,使用线程池可以很好的提升性能,特别是程序中涉及创建大量生命周期很短暂的线程时. 与数据库连接池类似,线 ...

  5. java并发编程基础——线程同步

    线程同步 一.线程安全问题 如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码.如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安 ...

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

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

  7. Java并发编程基础

    Java并发编程基础 1. 并发 1.1. 什么是并发? 并发是一种能并行运行多个程序或并行运行一个程序中多个部分的能力.如果程序中一个耗时的任务能以异步或并行的方式运行,那么整个程序的吞吐量和可交互 ...

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

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

  9. 并发-Java并发编程基础

    Java并发编程基础 并发 在计算机科学中,并发是指将一个程序,算法划分为若干个逻辑组成部分,这些部分可以以任何顺序进行执行,但与最终顺序执行的结果一致.并发可以在多核操作系统上显著的提高程序运行速度 ...

随机推荐

  1. RGB-D对红外热像仪和毫米波雷达标定

    RGB-D对红外热像仪和毫米波雷达标定 Extrinsic Calibration of Thermal IR Camera and mmWave Radar by Exploiting Depth ...

  2. 基于SSL(TLS)的HTTPS网页下载——如何编写健壮的可靠的网页下载

    源码下载地址案例开发环境:VS2010本案例未使用openssl库,内部提供了sslite.dll库进行TLS会话,该库提供了ISSLSession接口用于建立SSL会话. HTTP协议很简单,写个简 ...

  3. Spring Cloud08: Hystrix 容错机制与数据监控

    一.概述 容错机制是指的是在一个分布式系统中,每个微服务之间是相互调用的,并且他们之间相互依赖,而实际的运行情况中,可能会因为各种原因导致某个微服务不可用,那么依赖于这个微服务的其他微服务就可能出现响 ...

  4. 性能工具之Jmeter-Dubbo脚本开发

    内容目录: 1.idea 环境项目部署 2.nacos 环境部署 3.dubbo插件部署 4.不带参数请求 5.带参参数请求 Apache Dubbo  是一款高性能.轻量级的开源Java RPC框架 ...

  5. 自动化工具之Appium工具简单介绍

    背景 自动化,性能测试,接口测试,开发平台等工作,到底测试的价值在哪里,其实价值来源不断充实与为大众服务,今天简单介绍ui小工具appium攻击. 简单介绍 Appium 是一个自动化测试开源工具,支 ...

  6. ES6 学习笔记之对象的拓展

    1.属性的简洁表示法 ES6 允许直接写入变量和函数,作为对象的属性和方法.这样书写更加简洁. const foo = 'bar'; const baz = {foo}; baz //{foo: &q ...

  7. 计算机网络的参考模型与5G协议

     一.分层思想 二.OSI七层参考模型 三.FPC/IP五层模型 四.数据的封装过程与PDU(协议数据单元) 五.数据的解封装过程 六.各层间通信与设备与层的对应关系 七.总结 一.分层思想 将复杂的 ...

  8. C#调用JAVA(二)调用方法

    上期我们创建了jar包并放到了unity中,那么我们继续 如果您还没有看上一期请先看上一期,这是链接 C#调用JAVA(一)制作jar包 - 执著GodShadow - 博客园 (cnblogs.co ...

  9. 30、LNAP(php和nginx相关优化)

    30.1.php-fpm.conf参数优化: [global] pid = run/php-fpm.pid #php后台运行pid路径 error_log = log/php-fpm.log #php ...

  10. 15、oracle多表查询

    15.0.实验建表: --父表 create table class( id number(10)constraint class_id_pk primary key, class_name varc ...