1、线程之间的通信
线程通信概念:线程是操作系统中独立的个体,但这些个体如果不是经过特殊的处理就不能成功一个整体,线程间的通信就是成为整体的必用方式之一。当线程存在通信指挥,系统间的交互就会更强大,在提高cpu利用率的同时还会使开发人员对线程任务在处理的过程中进行有效的把控与监督。
使用 wait / notify 方法实现线程间的通信。(注意这两个方法都是object的类的方法,所以java所有的对象都会提供这两个方法)
1) wait 和 notify 必须配合 synchronized 关键字使用。
2) wait 方法释放锁,notify方法不释放锁。
/**
* 此段代码的目的是实现T1线程不断向list里面添加元素,当元素达到一定数量后被T2线程检测到。从而终止T2。
*
* 此段代码没有使用 wait 和 notify .T2线程是使用一个死循环来检测list里面的值。这样的内存消耗是不可取的。
*
* 所以后面的程序,我们需要使用 wait 和 notify 来解决此问题。
*/
public class ListAdd1 {
private volatile static List list = new ArrayList();
public void add(){
list.add("bjsxt");
}
public int size(){
return list.size();
}
public static void main(String[] args) {
final ListAdd1 list1 = new ListAdd1();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
for(int i = 0; i <10; i++){
list1.add();
System.out.println("当前线程:" + Thread.currentThread().getName() + "添加了一个元素..");
Thread.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
while(true){
if(list1.size() == 5){
System.out.println("当前线程收到通知:" + Thread.currentThread().getName() + " list size = 5 线程停止..");
throw new RuntimeException();
}
}
}
}, "t2");
//T1,T2启动,T1不断叠加元素,当list达到5时,T2触发条件停止,实现了线程交互。
t1.start();
t2.start();
}
}
改造上面的代码,使用 wait 和 notify
。
/**
* wait notfiy 方法,wait释放锁,notfiy不释放锁
*
* @author alienware
*/
public class ListAdd2 {
private volatile static List list = new ArrayList();
public void add() {
list.add("bjsxt");
}
public int size() {
return list.size();
}
public static void main(String[] args) {
final ListAdd2 list2 = new ListAdd2();
// 1 实例化出来一个 lock
// 当使用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++) {
list2.add();
System.out.println("当前线程:" + Thread.currentThread().getName() + "添加了一个元素..");
Thread.sleep(500);
if (list2.size() == 5) {
System.out.println("已经发出通知..");
//唤醒等待的程序,但是并没有把锁释放。notify方法是不释放锁的,要等for循环完10次才会释放锁。
lock.notify();
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
if (list2.size() != 5) {
try {
System.out.println("t2进入...");
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("当前线程:" + Thread.currentThread().getName() + "收到通知线程停止..");
throw new RuntimeException();
}
}
}, "t2");
//T2线程先执行,然后获得了锁。因为T2线中有lock.wait(),所以执行途中就会释放锁,同时进入等待状态。
t2.start();
//T1线程开始执行,因为上面T2已经释放了锁,所以此时T1会拿到锁,进入添加元素操作。添加到第五个元素的时候,因为有lock.notify()代码。所以会唤醒等待的程序,但此时并不会马上释放锁。
t1.start();
}
}
上面代码有一个弊端,也是 wait 和 notify 的弊端的问题,就也是通知不实时的问题。本来我们想要的结果是T1执行添加了5个元素就去通知T2的,但因为notify的不实时。导致必须要T1要执行完for循环才会回去执行T2。
所以,我们可以使用 CountDownLatch
解决上面的问题,性能上又会比wait 和 notify好些。CountDownLatch可以看做是 wait 和 notify的升级版
,不需要加锁。代码如下:
/**
* wait notfiy 方法,wait释放锁,notfiy不释放锁
*
* @author alienware
*/
public class ListAdd2 {
private volatile static List list = new ArrayList();
public void add() {
list.add("bjsxt");
}
public int size() {
return list.size();
}
public static void main(String[] args) {
final ListAdd2 list2 = new ListAdd2();
final CountDownLatch countDownLatch = new CountDownLatch(1);
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
list2.add();
System.out.println("当前线程:" + Thread.currentThread().getName() + "添加了一个元素..");
Thread.sleep(500);
if (list2.size() == 5) {
System.out.println("已经发出通知..");
countDownLatch.countDown();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}, "t1");
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
if (list2.size() != 5) {
try {
System.out.println("t2进入...");
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("当前线程:" + Thread.currentThread().getName() + "收到通知线程停止..");
throw new RuntimeException();
}
}, "t2");
t2.start();
t1.start();
}
}
2、使用 wait 和 notify 实现一个 阻塞消息队列
。
public class MyQueue {
//1 需要一个承装元素的集合
private LinkedList<Object> list = new LinkedList<Object>();
//2 需要一个计数器
private AtomicInteger count = new AtomicInteger(0);
//3 需要制定上限和下限
private final int minSize = 0;
private final int maxSize ;
//4 构造方法
public MyQueue(int size){
this.maxSize = size;
}
//5 初始化一个对象 用于加锁
private final Object lock = new Object();
//put(anObject): 把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻断,直到BlockingQueue里面有空间再继续.
public void put(Object obj){
synchronized (lock) {
while(count.get() == this.maxSize){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//1 加入元素
list.add(obj);
//2 计数器累加
count.incrementAndGet();
//3 通知另外一个线程(唤醒)
lock.notify();
System.out.println("新加入的元素为:" + obj);
}
}
//take: 取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻断进入等待状态直到BlockingQueue有新的数据被加入.
public Object take(){
Object ret = null;
synchronized (lock) {
while(count.get() == this.minSize){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//1 做移除元素操作
ret = list.removeFirst();
//2 计数器递减
count.decrementAndGet();
//3 唤醒另外一个线程
lock.notify();
}
return ret;
}
public int getSize(){
return this.count.get();
}
public static void main(String[] args) {
final MyQueue mq = new MyQueue(5);
mq.put("a");
mq.put("b");
mq.put("c");
mq.put("d");
mq.put("e");
System.out.println("当前容器的长度:" + mq.getSize());
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
mq.put("f");
mq.put("g");
}
},"t1");
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
Object o1 = mq.take();
System.out.println("移除的元素为:" + o1);
Object o2 = mq.take();
System.out.println("移除的元素为:" + o2);
}
},"t2");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
项目上一般不会这样用,上述代码只是展示了一个队列是如何实现的。项目中我们一般使用java并发包下无界队列和有界队列队列或者使用第三方框架,有或者中间件去实现队列。