线程一1.0 线程的通信--多线程间的通信

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/insis_mo/article/details/88528101

上面学了两个线程间的通讯,只有一个线程对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 中所有的线程都会被弹出   如下:

猜你喜欢

转载自blog.csdn.net/insis_mo/article/details/88528101