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并发编程基础 并发 在计算机科学中,并发是指将一个程序,算法划分为若干个逻辑组成部分,这些部分可以以任何顺序进行执行,但与最终顺序执行的结果一致.并发可以在多核操作系统上显著的提高程序运行速度 ...
随机推荐
- gst-crypto GStreamer插件
gst-crypto GStreamer插件 内容 1. gst-crypto概述 1.1gst-crypto GStreamer插件功能 1.2用例范例 2. GStreamer插件支持 3. 在本 ...
- 《python网络数据采集》笔记1
第一部分-创建爬虫 1.urllib 1)urllib.request request.urlopen(url) request.urlretrieve 可以根据文件的 URL 下载文件 2)urll ...
- 【SQLite】教程03-SQLite语法
注释: sqlite>.help -- 这是一个简单的注释 SQLite ANALYZE 语句: 收集有关表和索引的统计信息,并将收集的信息存储在数据库的内部表中 ANALYZE; or ANA ...
- Pipeline模式与Factory+Provider模式的应用
前言 我正在写FastGithub这个小麻雀项目,里面主要涉及了Pipeline模式和Factory+Provider模式,这两种设计模式,让这个项目在"ip扫描"和"i ...
- SqlServer的order by问题
如果指定了 SELECT DISTINCT,那么 ORDER BY 子句中的项就必须出现在选择列表中.这是我昨天写sql的时候遇到的,主要是因为最近在做一个数据库的迁移与更换,原来MySQL的数据库全 ...
- Unity中的.Meta文件
.meta文件是用于辅助管理Unity资源文件的文件,删除后,Unity会自动生成,里面记录了各个资源Inspector的信息,属性等等,Unity是不会改变源资源文件的,没有意义,它是靠.meta文 ...
- 我是怎么写 Git Commit message 的?
目录 作用 用的什么规范? type scope subject body footer 参考文章 用的什么辅助工具? 作用 编写格式化的 commit message 能够大大提高代码的维护效率. ...
- 1、Linux下源码编译安装PostgreSQL
操作系统:Centos7 说明:postgresql必须在postgres用户下初始化数据库和启动,否则报错. PostgreSQL的特性 PostgreSQL是一种几乎可以运行在各种平台上的免费的开 ...
- 3、mysql的多实例配置(3)
8.mysql多实例故障排错:
- xf浅谈_最短路
最短路问题(short-path problem):最短路问题是图论研究中的一个经典算法问题,指在寻找图(由结点和路径组成的)中两结点之间的最短路径.算法具体的形式包括: 1.确定起点的最短路径问题 ...