一、线程间通信概念
线程是操作 系统中独立的个体,这些个体如果不经过特殊的处理就不能成为一个整体,线程间的 通信就成为整体的必用方式之一。系统要实现某个全局功能必定要需要各个子模块之间的协调和配合,就像一个团队要完成某项任务的时候需要团队各个成员之间密切配合一样。而对于系统中的各个子线程来说,如果要完成一个系统功能,同样需要各个线程的配合,这样就少不了线程之间的通信与协作。当线程存在通信指挥,系统间的交互性会更强大,在提高cpu利用率的同时还会使开发人员对线程任务在处理的过程中进行有效的把控与监督。
常见的线程之间通信方式有如下几种:
(1)wait和notify、notifyAll
(2)await和signal、signalAll
(3)sleep/yield/join
(4)CyclicBarrier栅栏
(5)CountDownLatch闭锁
(6)Semaphore信号量
1.1 wait/notify、notifyAll
使用wait/notify、notifyAll方法实现线程间的通信。(注意这两个方法是Object类的方法,换句话说Java为所有的对象都提供了这两个方法)
注意:
a.wait和notify、notifyAll必须配合synchronized关键字使用
b.await方法释放锁,notify方法不释放锁
面试例子:
package cn.itcast_03;
import java.util.ArrayList;
import java.util.List;
public class ListAdd1 {
private volatile static List list1=new ArrayList();
public void add(){
list1.add("demo1");
}
public int size(){
return list1.size();
}
public static void main(String[] args) {
final ListAdd1 listadd1=new ListAdd1();
Thread t1=new Thread(new Runnable(){
@Override
public void run() {
try {
for(int i=0;i<10;i++){
listadd1.add();
System.out.println("当前线程:"+Thread.currentThread().getName()+"添加了一个元素。。。");
Thread.sleep(500);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
},"t1");
Thread t2=new Thread(new Runnable(){
@Override
public void run() {
while(true){
if(listadd1.size()==5){
System.out.println("当前线程收到通知:"+Thread.currentThread().getName()+"listadd1 size=5线程停止 ");
throw new RuntimeException();
}
}
}
},"t2");
t2.start();
t1.start();
}
}
运行结果:
当前线程:t1添加了一个元素。。。
当前线程:t1添加了一个元素。。。
当前线程:t1添加了一个元素。。。
当前线程:t1添加了一个元素。。。
当前线程:t1添加了一个元素。。。
当前线程收到通知:t2listadd1 size=5线程停止
Exception in thread "t2" java.lang.RuntimeException
at cn.itcast_03.ListAdd1$2.run(ListAdd1.java:47)
at java.lang.Thread.run(Thread.java:745)
当前线程:t1添加了一个元素。。。
当前线程:t1添加了一个元素。。。
当前线程:t1添加了一个元素。。。
当前线程:t1添加了一个元素。。。
当前线程:t1添加了一个元素。。。
上面的方法while(true)判断一直在执行比较耗费资源,用一个线程的机制去改进。
package cn.itcast_03;
import java.util.ArrayList;
import java.util.List;
public class ListAdd2 {
private volatile static List list2=new ArrayList();
public void add(){
list2.add("demo1");
}
public int size(){
return list2.size();
}
public static void main(String[] args) {
final ListAdd2 listadd2=new ListAdd2();
//实例化一把锁
//当使用wait和notify 的时候,一定要配合着Synchronized关键字去使用
final Object lock=new Object();
Thread t1=new Thread(new Runnable(){
@Override
public void run() {
try {
synchronized(lock){
for(int i=0;i<10;i++){
listadd2.add();
System.out.println("当前线程:"+Thread.currentThread().getName()+"添加了一个元素。。。");
Thread.sleep(500);
if(listadd2.size()==5){
System.out.println("已经发出通知");
//notify不释放锁
lock.notify();
}
}
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
},"t1");
Thread t2=new Thread(new Runnable(){
@Override
public void run() {
synchronized(lock){
if(listadd2.size()!=5){
try{
//wait方法释放锁
lock.wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
System.out.println("当前线程收到通知:"+Thread.currentThread().getName()+"listadd2 size=5线程停止 ");
throw new RuntimeException();
}
}
},"t2");
t2.start();
t1.start();
}
}
运行结果:
当前线程:t1添加了一个元素。。。
当前线程:t1添加了一个元素。。。
当前线程:t1添加了一个元素。。。
当前线程:t1添加了一个元素。。。
当前线程:t1添加了一个元素。。。
已经发出通知
当前线程:t1添加了一个元素。。。
当前线程:t1添加了一个元素。。。
当前线程:t1添加了一个元素。。。
当前线程:t1添加了一个元素。。。
当前线程:t1添加了一个元素。。。
当前线程收到通知:t2listadd2 size=5线程停止
Exception in thread "t2" java.lang.RuntimeException
at cn.itcast_03.ListAdd2$2.run(ListAdd2.java:65)
at java.lang.Thread.run(Thread.java:745)
注意:t2一点要先启动执行,不然t1执行过之后size=10,t2就看不出效果。由于lock的wait方法不释放锁,所以一直要t1执行完了之后t2才能获取锁去执行。所以这里看到的结果不是实时。
package cn.itcast_03;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class ListAdd3 {
private volatile static List list2=new ArrayList();
public void add(){
list2.add("demo1");
}
public int size(){
return list2.size();
}
public static void main(String[] args) {
final ListAdd3 listadd3=new ListAdd3();
//实例化一把锁
//当使用wait和notify 的时候,一定要配合着Synchronized关键字去使用
//final Object lock=new Object();
final CountDownLatch countDownLatch=new CountDownLatch(1);
Thread t1=new Thread(new Runnable(){
@Override
public void run() {
try {
//synchronized(lock){
for(int i=0;i<10;i++){
listadd3.add();
System.out.println("当前线程:"+Thread.currentThread().getName()+"添加了一个元素。。。");
Thread.sleep(500);
if(listadd3.size()==5){
System.out.println("已经发出通知");
//notify不释放锁
// lock.notify();
countDownLatch.countDown();
}
// }
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
},"t1");
Thread t2=new Thread(new Runnable(){
@Override
public void run() {
//synchronized(lock){
if(listadd3.size()!=5){
try{
//wait方法释放锁
//lock.wait();
countDownLatch.await();
}catch(InterruptedException e){
e.printStackTrace();
}
}
System.out.println("当前线程收到通知:"+Thread.currentThread().getName()+"listadd2 size=5线程停止 ");
throw new RuntimeException();
//}
}
},"t2");
t2.start();
t1.start();
}
}
运行结果:
当前线程:t1添加了一个元素。。。
当前线程:t1添加了一个元素。。。
当前线程:t1添加了一个元素。。。
当前线程:t1添加了一个元素。。。
当前线程:t1添加了一个元素。。。
已经发出通知
当前线程:t1添加了一个元素。。。
当前线程收到通知:t2listadd2 size=5线程停止
Exception in thread "t2" java.lang.RuntimeException
at cn.itcast_03.ListAdd3$2.run(ListAdd3.java:70)
at java.lang.Thread.run(Thread.java:745)
当前线程:t1添加了一个元素。。。
当前线程:t1添加了一个元素。。。
当前线程:t1添加了一个元素。。。
当前线程:t1添加了一个元素。。。
面试题:使用wait/notify模拟一个Queue(BlockQueue)
a)什么是阻塞队列(后面会专门总结)
阻塞队列是一个支持两个附加操作的队列。这两个附加操作是在队列为空时,获取元素的线程会等待队列变为非空。当队列满时,存储元素的线程会等待队列可用。阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
b)思路
b.1 队列是一个容器,故需要定义一个集合
b.2 需要一个计数器来统计容器的元素个数
b.3 需要设置容器队列的上限和下限
b.4 wait/notify必须配合 synchronized使用,wait进入阻塞状态释放锁而notify不释放锁。wait和notify都是Object类,所以需要定义一把Object类锁来调用这两个方法
b.5 put方法:判断计数器的大小是否等于容器队列的上限,是的话进入等待否则往队列添加元素,计数器做累加操作,最后通知另一个线程
b.6 take方法:判断计数器的大小是否等于容器队列的下限,是的话进入等待否则移除容器队列的第一个元素,计数器做递减操作,最后通知另一个线程
package cn.itcast_04;
import java.util.LinkedList;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 使用wait/notify模拟一个阻塞队列
*
* @author jack
*
*/
public class MyQueue {
//1. 需要一个承装元素的集合
private LinkedList list=new LinkedList<>();
//2.需要一个计数器(该类具备原子性)
private AtomicInteger count=new AtomicInteger();
//3.需要指定队列容器的上限和下限
private final int maxSize;
private final int minSize=0;
//4.构造方法
public MyQueue(int size){
maxSize=size;
}
//5.定义一把锁来调用wait/notify
private Object lock=new Object();
//6.put(Object): 把Object加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断,直到BlockingQueue里面有空间再继续
public void put(Object obj){
synchronized(lock){
//6.1集合是否满了
while(count.get()==this.maxSize){
try {
//6.2进入阻塞状态(释放锁)
lock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//6.3添加元素操作
list.add(obj);
//6.4计数器累加
count.incrementAndGet();
//6.5通知另一个线程可以取数据了(唤醒功能)
lock.notify();
System.out.println("新加入的元素为"+obj);
}
}
//7.take: 取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入
public Object take(){
Object result=null;
synchronized(lock){
//7.1集合没有数据了
while(count.get()==this.minSize){
try {
//7.2进入阻塞状态
lock.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
//7.3移除元素操作(移除第一个)
result= list.removeFirst();
//7.4计数器递减
count.decrementAndGet();
//7.5通知另一个线程可以添加元素了(唤醒功能)
lock.notify();
}
return result;
}
//8 得到队列的长度
public int getSize(){
return this.count.get();
}
}
测试类:
package cn.itcast_04;
import java.util.concurrent.TimeUnit;
public class MyQueueTest {
public static void main(String[] args) {
MyQueue queue=new MyQueue(5);
queue.put("孙悟空");
queue.put("猪八戒");
queue.put("沙和尚");
queue.put("唐三藏");
queue.put("小白龙");
System.out.println("------当前队列的长度--------" + queue.getSize());
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
queue.put("观音");
queue.put("如来");
}
},"t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
Object o1 = queue.take();
System.out.println("-------移除的元素是-------" + o1);
Object o2 = queue.take();
System.out.println("-------移除的元素是-------" + o2);
}
},"t2");
t1.start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t2.start();
}
}
新加入的元素为孙悟空
新加入的元素为猪八戒
新加入的元素为沙和尚
新加入的元素为唐三藏
新加入的元素为小白龙
------当前队列的长度--------5
新加入的元素为观音
-------移除的元素是-------孙悟空
新加入的元素为如来
-------移除的元素是-------猪八戒