线程一1.0 线程的通信--多线程间的通信
上面学了两个线程间的通讯,只有一个线程对Eventqueue进行offer操作,也只有一个线程对Eventqueue进行take操作,如果多个线程同时进行offer和take,那么上面程序就会出现问题,
1 notifyAll方法
多线程通信需要用到object的notifyAll 方法该方法和notify方法比较类似,都可以唤醒由于调用了wait 方法而阻塞的线程,但是notify方法只能唤醒其中一个线程,而notifyall 方法则可以唤醒全部的阻塞线程,同样被唤醒的线程仍要继续争抢monitor的锁、
2生产者和消费者上面我们定义了一个Eventqueue,该队列在多个线程同时并发的情况下会出现数据不一致的问题,可以自己去增加Eventqueue的线程数量进行测试,我测试之后出现了数据不一致的情况,大致分为两类,一是linkedLIset 中没有元素的时候仍然调用了removeFirst方法,二是当linkedLIst中元素超过了10个时候仍然调用了addlast 方法 ,下面进行分析:
(1)linkedList 为空执行了removeFirst方法
也许你会有疑问,Eventqueue中的方法增加了synchronize 数据同步,为何还出现数据不一致的情况?假设Eventqueue中的元素为空,两个线程执行take方法时分别调用wait方法进入了阻塞中,另外一个offer线程执行addlast方法之后唤醒了其中一个阻塞的take线程,该线程顺利消费了一个元素之后恰巧在唤醒了一个take线程,这时就会导致执行空 LinkedList 的removeFirst方法
(2) LinkedList 元素为10 时执行addLast方法
假设某个时刻Eventqueue 中存在10个Event数据,其中两个线程执行offer方法时分别调用wait方法进入了阻塞中,另外一个线程执行take 消费了一个Event元素并且唤醒了一个offer线程,而该offer线程执行addlast方法后,queue中元素为10 ,并且再次执行唤醒方法,恰巧另外一个offer线程也被唤醒,因此可以绕开阈值检查Eventqueue().size>=max,致使Eventqueue元素超过10个,
(3) 改进
实例如下:
public void offer(Event event) {
synchronized (eventQueue) {
while (eventQueue.size() > max) {
try {
console("the queue is full.");
eventQueue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
console("the new event is submitted");
eventQueue.addLast(event);
eventQueue.notifyAll();
}
}
public Event take() {
synchronized (eventQueue) {
while (eventQueue.isEmpty()) {
try {
console("the queue is empty");
eventQueue.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Event event=eventQueue.removeFirst();
this.eventQueue.notifyAll();
console("the enent "+event+"====ishandled");
return event;
}
}
只要把临界值得判断if改为 while 将notify 改为 notifyAll就行
ps 题外话(多线程中用条件对象,通常对await的调用应该在
while(!(ok to proceed))
{
condition.await();
}
在代码中实验了下,在调用await()之前打印一下"thread will wait",发现用while时会打印大量的"thread will wait",而if时会少很多。这样是不是表示while和if的机制会不一样。想知道这是为什么推荐用while,它和if有啥区别
因为可能有多个线程await在这里,一个notifyAll,全部唤醒,又要重新竞争,先得到时间片的线程向下运行了,其他线程又需要回到await上。如果不是while,而是if,所有的线程都会走下去了
举个例子,一个生产者消费者模型的任务队列,一个生产者一次可能放入多个任务,然后用notifyAll通知消费者,但是并非所有被唤醒的消费者都能取到一个任务,那么队列被读空了之后的消费者肯定得继续await。如果你用if来判断,这个消费者第二次被notify的时候就不会再次判断!(ok to proceed)这个条件了,如果这个时候这个消费者又一次没抢到任务,但是代码还是往下执行了,轻则空指针异常,重了干出什么事情来都说不定了。
所以必须用while来检查!(ok to proceed),这样可以保证每次被唤醒都会检查一次条件。)
二:线程休息室 wait set
在虚拟机规范中存在一个 wait set(又被称为线程休息室)的概念,线程调用了某个对象的wait方法之后都会被加入与该对象的monitor关联的wait set 中,并且释放monitor 的所有权。
下图是若干个线程调用了wait 方法之后被加入与monitor 关联的wait set中,待另外一个线程调用改monitor的notify方法之后,其中一个线程会从wait set 中弹出,至于是随机还是先进先出方式弹出,虚拟机规范同样没给出强制要求
而执行notifyAll 方法不需要考虑那个线程被弹出,因为wait set 中所有的线程都会被弹出 如下: