线程通信

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

一、传统的线程通信

传统的线程通信主要是通过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. CloudHub概述

    CloudHub概述 CloudHub CloudHub是cloudcore的一个模块,是Controller和Edge端之间的中转.它同时支持基于websocket的连接以及QUIC协议访问.Edg ...

  2. .Net RabbitMQ实战指南——进阶(一)

    备份交换器 备份交换器,英文名称为Alternate Exchange,简称AE.通过在声明交换器(调用channel.ExchangeDeclare方法)时添加alternate-exchange参 ...

  3. 构造无限级树的框架套路,附上python/golang/php/js实现

    目录 前言 需求 数据 结果 框架 递归框架 迭代框架 递归框架实现 python golang php js 迭代框架实现 python golang php js 前言 框架思维非常重要,和语言无 ...

  4. 实验5、Flask设计模式和Web服务体验

    1. 实验内容 Flask appbuilder包括基本的表单验证,内置管理模块.本节主要学习Flask appbuilder的后台管理模块使用和对Flask设计模式拓展作简要介绍 2. 实验要点 掌 ...

  5. 浅谈lambda表达式&lt;最通俗易懂的讲解

    Java8发布已经有一段时间了,这次发布的改动比较大,很多人将这次改动与Java5的升级相提并论.Java8其中一个很重要的新特性就是lambda表达式,允许我们将行为传到函数中.想想看,在Java8 ...

  6. PRVF-0002: could not retrieve local nodename报错解决

     

  7. 进程与线程 .Net Core系列-多线程

    进程与线程 进程: 狭义定义:进程是正在运行的程序的实例 广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动.它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分 ...

  8. Linux 中使用 QT Charts 显示温度传感器

    前一篇笔记中实现了QT的Label控件显示CPU的温度,只能显示当前的温度,并不能反映CPU温度的变化情况,现在来实现使用QT Charts的曲线图实现一个实时曲线来显示CPU的温度. 添加对Qt C ...

  9. 六、JavaSE语言基础之数组

    一维数组(关键字[]) 关于数组的一些概念: 数组是多个基本数据有机组合形成一个复杂数据,是一个引用数据类型数据. 数组:装指定数量元素类型相同的数据的容器. 元素:在数组中,数组中的每个数据称之为数 ...

  10. JPA事务中的异常最后不也抛出了,为什么没被catch到而导致回滚?

    上周,我们通过这篇文章<为什么catch了异常,但事务还是回滚了?>来解释了,之前test4为什么会回滚的原因. 但还是收到了很多没有理解的反馈,主要是根据前文给出的线索去跟踪,是获得到了 ...