精华内容
下载资源
问答
  • 线程等待通知机制

    千次阅读 2018-07-23 09:06:01
    等待通知机制,是指一个线程A调用了对象O的wait方法进入等待状态,而另一个线程调用了对象O的notify或者notifyAll方法,线程A收到通知后从对象O的wait方法返回,进而执行后续操作 方法名称 描述 notify() ...

    等待通知机制,是指一个线程A调用了对象O的wait方法进入等待状态,而另一个线程调用了对象O的notify或者notifyAll方法,线程A收到通知后从对象O的wait方法返回,进而执行后续操作

    方法名称描述
    notify()通知一个在对象上对待的线程,使其从wait方法返回,而返回的前提是该线程获取到了对象的锁
    notifyAll()通知所有等待在该对象上的线程
    wait()调用该方法的线程进入WAITING状态,只有等待另一个线程的通知或中断才会被返回,需要注意,调用wait方法后,会释放对象锁
    wait(long)超时等待一段时间,这里参数时间是毫秒,如果没有通知就超时返回
    wait(long,int)对于超时时间更细粒度的控制,可以达到纳秒

    代码示例

    package com.demo.demo4;
    
    import java.text.SimpleDateFormat;
    import java.util.Date;
    
    public class WaitNotify {
    
    	static boolean flag = true;
    
    	static Object lock = new Object();
    
    	public static void main(String[] args) {
    
    		Thread waitThread = new Thread(new Wait(),"waitThread");
    		waitThread.start();
    		try {
    			Thread.sleep(1);
    			Thread notifyThread = new Thread(new Notify(),"notifyThread");
    			notifyThread.start();
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    	}
    
    	static class Wait implements Runnable{
    
    		@Override
    		public void run() {
    			//加锁,拥有lock的Monitor
    			synchronized(lock) {
    				//当条件不满足,继续wait,同时释放了lock的锁
    				while(flag) {
    					try {
    						System.out.println(Thread.currentThread()+"flag is true.wait@"+new SimpleDateFormat("HH:mm:ss").format(new Date()));
    						lock.wait();
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    				}
    				//满足条件,完成工作
    				System.out.println(Thread.currentThread()+"flag is false.wait@"+new SimpleDateFormat("HH:mm:ss").format(new Date()));
    			}
    
    		}
    
    	}
    
    	static class Notify implements Runnable{
    
    		@Override
    		public void run() {
    			//加锁,拥有lock的Monitor
    			synchronized (lock) {
    				//获取lock的锁,然后进行通知,通知不会释放lock的锁
    				//直到获取当前线程释放lock后,WaitThread才能从wait方法返回
    				System.out.println(Thread.currentThread()+"hold lock.nofity @"+new SimpleDateFormat("HH:mm:ss").format(new Date()));
                    lock.notifyAll();
                    flag = true;
                    try {
    					Thread.sleep(5);
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				}
    			}	
    			//在此加锁
    			synchronized(lock) {
    				System.out.println(Thread.currentThread()+"hold lock.nofity @"+new SimpleDateFormat("HH:mm:ss").format(new Date()));
                    try {
    					Thread.sleep(5);
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				}
    			}
    		}
    
    	}
    }
    

    一个线程修改了一个对象的值,而另一个线程感知到了变化,然后进行相应的操作,整个过程开始于一个线程,而最终执行的又是另一个线程

     

    1、使用wait(),notify()和notifyAll()时需要先调用对象的锁

    2、调用wait方法后,线程状态由RUNING变为WAITING,并将当前线程放置到对象的等待队列

    3、notify()和notifyAll()方法调用后,等待线程依旧不会从wait返回,需要调用notify()和notifyAll()线程释放锁之后,等待线程才有机会从wait()返回

    4、notify()方法将等待队列中的一个线程从等待队列中移动到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列中,被移动的队列线程状态由WAITING变为BLOCKED

    5、从wait()方法返回的前提就是获取了调用的对象的锁

    展开全文
  • 线程文件编写器,使用等待通知 这段代码使用等待通知机制实现线程以模拟同时写入文件的线程。
  • [JAVA]等待通知机制的2种实现

    千次阅读 2017-12-28 22:28:36
    等待通知机制首先介绍下什么是等待通知机制。这里举一个生活的例子。大家去餐馆吃饭的时候,会取号进行”等待“。等到号了,餐馆工作人员会”通知“你前去就餐。这就是一个简单的等待通知的例子。好了,接下来介绍下...

    最近笔者在空闲之余,温习了下JAVA多线程编程。经常看,却经常忘记。可能是由于用的不多的缘故吧。这里针对多线程的等待通知的机制的2种实现进行总结。加深理解和认识。

    等待通知机制

    首先介绍下什么是等待通知机制。这里举一个生活的例子。

    大家去餐馆吃饭的时候,会取号进行”等待“。等到号了,餐馆工作人员会”通知“你前去就餐。

    这就是一个简单的等待通知的例子。好了,接下来介绍下它的2种实现。

    wait()和notify()实现等待通知机制

    源码见Github

    去餐馆就餐的客人(等待者)

    package com.zju.javastudy.waitnotify.demo1;
    
    /**
     * @author Arthur
     * @Date 2017/12/28
     * @Decription: 饿着的人,去餐馆取号,等待者
     */
    public class HungryPeople implements Runnable {
        /**
         * 座位
         */
        private Object seat;
    
        public HungryPeople(Object seat) {
            this.seat = seat;
        }
    
    
        @Override
        public void run() {
            synchronized (seat) {
                try {
                    System.out.println("HungryPeople:去餐馆吃饭,先取号...");
                    seat.wait();//等待叫号
                    System.out.println("HungryPeople:到号,有位置了!可以吃饭了!");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
        }
    }
    

    餐馆(通知者)

    package com.zju.javastudy.waitnotify.demo1;
    
    /**
     * @author Arthur
     * @Date 2017/12/28
     * @Decription: 餐馆,通知者
     */
    public class Restaurant implements Runnable {
        /**
         * 座位,实质就是锁
         */
        private Object seat;
    
        public Restaurant(Object o) {
            this.seat = o;
        }
    
    
        @Override
        public void run() {
            try {
                //模拟等待
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (seat) {
                System.out.println("Restaurant:有空位,通知客人来就餐...");
                seat.notify();//通知到号
                System.out.println("Restaurant:完成通知");
    
            }
    
        }
    
    }

    测试

    package com.zju.javastudy.waitnotidy.demo1;
    
    import com.zju.javastudy.waitnotify.demo1.HungryPeople;
    import com.zju.javastudy.waitnotify.demo1.Restaurant;
    import org.junit.Test;
    
    import static sun.jvm.hotspot.runtime.PerfMemory.start;
    
    /**
     * @author Arthur
     * @Date 2017/12/28
     * @Decription: 餐馆取号吃饭的测试例子
     */
    public class WaitNotidyTest1 {
    
        @Test
        public void test() throws InterruptedException {
            Object seat = new Object();//座位
            Restaurant restaurant = new Restaurant(seat);
            HungryPeople hungryPeople = new HungryPeople(seat);
            Thread hungryPeopleThread = new Thread(hungryPeople);
    
            hungryPeopleThread.start();
            Thread restaurantThread = new Thread(restaurant);
            restaurantThread.start();
    
            //子线程结束后,再结束main线程
            hungryPeopleThread.join();
            restaurantThread.join();
        }
    }

    结果

    这里写图片描述

    使用Condition实现部分通知

    源码见Github

    去餐馆就餐的客人(等待者)

    package com.zju.javastudy.waitnotify.demo2;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @author Arthur
     * @Date 2017/12/28
     * @Decription: 饿着的人,去餐馆取号,等待者
     */
    public class HungryPeople implements Runnable {
        /**
         * 所有座位
         */
        private ReentrantLock allSeat;
    
        /**
         * 等待的桌(大桌/小桌)
         */
        private Condition seat;
    
    
        public HungryPeople(ReentrantLock allSeat,Condition seat) {
            this.allSeat = allSeat;
            this.seat = seat;
        }
    
    
        @Override
        public void run() {
            allSeat.lock();
            try {
                seat.await();
                System.out.println(Thread.currentThread().getName()+":等到位置");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                allSeat.unlock();
            }
    
        }
    }

    餐馆(通知者)

    package com.zju.javastudy.waitnotify.demo2;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @author Arthur
     * @Date 2017/12/28
     * @Decription: 餐馆,通知者
     */
    public class Restaurant implements Runnable {
    
        /**
         * 所有座位
         */
        private ReentrantLock allSeat;
    
        /**
         * 小桌
         */
        private Condition smallSeat;
    
        /**
         * 大桌
         */
        private Condition bigSeat;
    
        public Restaurant(ReentrantLock allSeat,Condition smallSeat, Condition bigSeat) {
            this.allSeat = allSeat;
            this.smallSeat = smallSeat;
            this.bigSeat = bigSeat;
        }
    
    
        @Override
        public void run() {
            //这里模拟通知大桌
            try {
                allSeat.lock();
                Thread.sleep(2000);
                System.out.println("Restaurant:有大桌空位,通知客人来就餐...");
                bigSeat.signalAll();
                System.out.println("Restaurant:完成大桌通知");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                allSeat.unlock();
            }
    
            //这里模拟通知小桌
            try {
                Thread.sleep(3000);
                allSeat.lock();
                System.out.println("Restaurant:有小桌空位,通知客人来就餐...");
                smallSeat.signalAll();
                System.out.println("Restaurant:完成小桌通知");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                allSeat.unlock();
            }
            }
    }

    测试

    package com.zju.javastudy.waitnotidy.demo2;
    
    import com.zju.javastudy.waitnotify.demo2.HungryPeople;
    import com.zju.javastudy.waitnotify.demo2.Restaurant;
    import org.junit.Test;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.ReentrantLock;
    
    /**
     * @author Arthur
     * @Date 2017/12/28
     * @Decription: 餐馆取号吃饭的测试例子
     */
    public class WaitNotidyTest2 {
    
        @Test
        public void test() throws InterruptedException {
            ReentrantLock allSeat = new ReentrantLock();//座位
            Condition smallSeat = allSeat.newCondition();//小桌
            Condition bigSeat = allSeat.newCondition();//大桌
    
            //实例化 餐馆,小桌客人和大桌客人
            Restaurant restaurant = new Restaurant(allSeat,smallSeat,bigSeat);
            HungryPeople hungryPeopleWaitSmall = new HungryPeople(allSeat,smallSeat);
            HungryPeople hungryPeopleWaitBig = new HungryPeople(allSeat,bigSeat);
    
            //小桌客人线程
            Thread hungryPeopleWaitSmallThread = new Thread(hungryPeopleWaitSmall);
            hungryPeopleWaitSmallThread.setName("smallSeatPeople");
    
            //大桌客人线程
            Thread hungryPeopleWaitBigThread = new Thread(hungryPeopleWaitBig);
            hungryPeopleWaitBigThread.setName("bigSeatPeople");
    
            //餐馆线程
            Thread restaurantThread = new Thread(restaurant);
    
            //开始模拟
            hungryPeopleWaitSmallThread.start();
            hungryPeopleWaitBigThread.start();
            restaurantThread.start();
    
            //子线程结束再结束main线程
            hungryPeopleWaitSmallThread.join();
            hungryPeopleWaitBigThread.join();
            restaurantThread.join();
        }
    }

    结果

    这里写图片描述

    这里通过2种实现,对等待通知进制进行了一个小结。例子可能不太恰当。欢迎批评指正。

    展开全文
  • Condition源码分析与等待通知机制

    千次阅读 多人点赞 2019-10-09 21:02:13
    文章目录Condition简介Condition实现原理分析等待队列await实现原理signal/signalAll实现原理await与signal/signalAll的结合思考一个例子 Condition简介 任何一个java对象都天然继承于Object类,在线程间实现通信的...

    Condition简介

    任何一个java对象都天然继承于Object类,在线程间实现通信的往往会应用到Object的几个方法,比如wait(),wait(long timeout),wait(long timeout, int nanos)与notify(),notifyAll()几个方法实现等待/通知机制,同样的, 在java Lock体系下依然会有同样的方法实现等待/通知机制。从整体上来看Object的wait和notify/notify是与对象监视器配合完成线程间的等待/通知机制,而Condition与Lock配合完成等待通知机制,前者是java底层级别的,后者是语言级别的,具有更高的可控制性和扩展性。两者除了在使用方式上不同外,在功能特性上还是有很多的不同:

    在这里插入图片描述

    Condition实现原理分析

    等待队列

    要想能够深入的掌握condition还是应该知道它的实现原理,现在我们一起来看看condiiton的源码。创建一个condition对象是通过lock.newCondition(),而这个方法实际上是会new出一个ConditionObject对象,该类是AQS(AQS的实现原理的文章)的一个内部类,有兴趣可以去看看。前面我们说过,condition是要和lock配合使用的也就是condition和Lock是绑定在一起的,而lock的实现原理又依赖于AQS,自然而然ConditionObject作为AQS的一个内部类无可厚非。我们知道在锁机制的实现上,AQS内部维护了一个同步队列,如果是独占式锁的话,所有获取锁失败的线程的尾插入到同步队列,同样的,condition内部也是使用同样的方式,内部维护了一个 等待队列,所有调用condition.await方法的线程会加入到等待队列中,并且线程状态转换为等待状态。另外注意到ConditionObject中有两个成员变量:

    /** First node of condition queue. */
    private transient Node firstWaiter;
    /** Last node of condition queue. */
    private transient Node lastWaiter;
    

    这样我们就可以看出来ConditionObject通过持有等待队列的头尾指针来管理等待队列。主要注意的是Node类复用了在AQS中的Node类,其节点状态和相关属性可以去看AQS的实现原理的文章,如果您仔细看完这篇文章对condition的理解易如反掌,对lock体系的实现也会有一个质的提升。Node类有这样一个属性:

    //后继节点
    Node nextWaiter;
    

    进一步说明,等待队列是一个单向队列,而在之前说AQS时知道同步队列是一个双向队列。接下来我们用一个demo,通过debug进去看是不是符合我们的猜想:

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(() -> {
                lock.lock();
                try {
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
            });
            thread.start();
        }
    }
    

    这段代码没有任何实际意义,甚至很臭,只是想说明下我们刚才所想的。新建了10个线程,没有线程先获取锁,然后调用condition.await方法释放锁将当前线程加入到等待队列中,通过debug控制当走到第10个线程的时候查看firstWaiter即等待队列中的头结点,debug模式下情景图如下:

    debug模式下情景图

    从这个图我们可以很清楚的看到这样几点:1. 调用condition.await方法后线程依次尾插入到等待队列中,如图队列中的线程引用依次为Thread-0,Thread-1,Thread-2…Thread-8;2. 等待队列是一个单向队列。通过我们的猜想然后进行实验验证,我们可以得出等待队列的示意图如下图所示:

    在这里插入图片描述

    同时还有一点需要注意的是:我们可以多次调用lock.newCondition()方法创建多个condition对象,也就是一个lock可以持有多个等待队列。而在之前利用Object的方式实际上是指在对象Object对象监视器上只能拥有一个同步队列和一个等待队列,而并发包中的Lock拥有一个同步队列和多个等待队列。示意图如下:

    AQS持有多个Condition.png

    如图所示,ConditionObject是AQS的内部类,因此每个ConditionObject能够访问到AQS提供的方法,相当于每个Condition都拥有所属同步器的引用。

    await实现原理

    当调用condition.await()方法后会使得当前获取lock的线程进入到等待队列,如果该线程能够从await()方法返回的话一定是该线程获取了与condition相关联的lock。接下来,我们还是从源码的角度去看,只有熟悉了源码的逻辑我们的理解才是最深的。await()方法源码为:

    public final void await() throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
    	// 1. 将当前线程包装成Node,尾插入到等待队列中
        Node node = addConditionWaiter();
    	// 2. 释放当前线程所占用的lock,在释放的过程中会唤醒同步队列中的下一个节点
        int savedState = fullyRelease(node);
        int interruptMode = 0;
        while (!isOnSyncQueue(node)) {
    		// 3. 当前线程进入到等待状态
            LockSupport.park(this);
            if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                break;
        }
    	// 4. 自旋等待获取到同步状态(即获取到lock)
        if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
            interruptMode = REINTERRUPT;
        if (node.nextWaiter != null) // clean up if cancelled
            unlinkCancelledWaiters();
    	// 5. 处理被中断的情况
        if (interruptMode != 0)
            reportInterruptAfterWait(interruptMode);
    }
    

    代码的主要逻辑请看注释,我们都知道当前线程调用condition.await()方法后,会使得当前线程释放lock然后加入到等待队列中,直至被signal/signalAll后会使得当前线程从等待队列中移至到同步队列中去,直到获得了lock后才会从await方法返回,或者在等待时被中断会做中断处理。那么关于这个实现过程我们会有这样几个问题:1. 是怎样将当前线程添加到等待队列中去的?2.释放锁的过程?3.怎样才能从await方法退出?而这段代码的逻辑就是告诉我们这三个问题的答案。具体请看注释,在第1步中调用addConditionWaiter将当前线程添加到等待队列中,该方法源码为:

    private Node addConditionWaiter() {
        Node t = lastWaiter;
        // If lastWaiter is cancelled, clean out.
        if (t != null && t.waitStatus != Node.CONDITION) {
            unlinkCancelledWaiters();
            t = lastWaiter;
        }
    	//将当前线程包装成Node
        Node node = new Node(Thread.currentThread(), Node.CONDITION);
        if (t == null)
            firstWaiter = node;
        else
    		//尾插入
            t.nextWaiter = node;
    	//更新lastWaiter
        lastWaiter = node;
        return node;
    }
    

    这段代码就很容易理解了,将当前节点包装成Node,如果等待队列的firstWaiter为null的话(等待队列为空队列),则将firstWaiter指向当前的Node,否则,更新lastWaiter(尾节点)即可。就是通过尾插入的方式将当前线程封装的Node插入到等待队列中即可,同时可以看出等待队列是一个不带头结点的链式队列,之前我们学习AQS时知道同步队列是一个带头结点的链式队列,这是两者的一个区别。将当前节点插入到等待对列之后,会使当前线程释放lock,由fullyRelease方法实现,fullyRelease源码为:

    final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();
            if (release(savedState)) {
    			//成功释放同步状态
                failed = false;
                return savedState;
            } else {
    			//不成功释放同步状态抛出异常
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }
    

    这段代码就很容易理解了,调用AQS的模板方法release方法释放AQS的同步状态并且唤醒在同步队列中头结点的后继节点引用的线程,如果释放成功则正常返回,若失败的话就抛出异常。到目前为止,这两段代码已经解决了前面的两个问题的答案了,还剩下第三个问题,怎样从await方法退出?现在回过头再来看await方法有这样一段逻辑:

    while (!isOnSyncQueue(node)) {
    	// 3. 当前线程进入到等待状态
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    

    很显然,当线程第一次调用condition.await()方法时,会进入到这个while()循环中,然后通过LockSupport.park(this)方法使得当前线程进入等待状态,那么要想退出这个await方法第一个前提条件自然而然的是要先退出这个while循环,出口就只剩下两个地方:1. 逻辑走到break退出while循环;2. while循环中的逻辑判断为false。再看代码出现第1种情况的条件是当前等待的线程被中断后代码会走到break退出,第二种情况是当前节点被移动到了同步队列中(即另外线程调用的condition的signal或者signalAll方法),while中逻辑判断为false后结束while循环。总结下,就是当前线程被中断或者调用condition.signal/condition.signalAll方法,当前节点移动到了同步队列后 ,这是当前线程退出await方法的前提条件。当退出while循环后就会调用acquireQueued(node, savedState),这个方法在介绍AQS的底层实现时说过了,若感兴趣的话可以去看这篇文章,该方法的作用是在自旋过程中线程不断尝试获取同步状态,直至成功(线程获取到lock)。这样也说明了退出await方法必须是已经获得了condition引用(关联)的lock。到目前为止,开头的三个问题我们通过阅读源码的方式已经完全找到了答案,也对await方法的理解加深。await方法示意图如下图:

    await方法示意图

    如图,调用condition.await方法的线程必须是已经获得了lock,也就是当前线程是同步队列中的头结点。调用该方法后会使得当前线程所封装的Node尾插入到等待队列中。

    超时机制的支持

    condition还额外支持了超时机制,使用者可调用方法awaitNanos,awaitUtil。这两个方法的实现原理,基本上与AQS中的tryAcquire方法如出一辙,关于tryAcquire可以仔细阅读这篇文章

    不响应中断的支持

    要想不响应中断可以调用condition.awaitUninterruptibly()方法,该方法的源码为:

    public final void awaitUninterruptibly() {
        Node node = addConditionWaiter();
        int savedState = fullyRelease(node);
        boolean interrupted = false;
        while (!isOnSyncQueue(node)) {
            LockSupport.park(this);
            if (Thread.interrupted())
                interrupted = true;
        }
        if (acquireQueued(node, savedState) || interrupted)
            selfInterrupt();
    }
    

    这段方法与上面的await方法基本一致,只不过减少了对中断的处理,并省略了reportInterruptAfterWait方法抛被中断的异常。

    signal/signalAll实现原理

    调用condition的signal或者signalAll方法可以将等待队列中等待时间最长的节点移动到同步队列中,使得该节点能够有机会获得lock。按照等待队列是先进先出(FIFO)的,所以等待队列的头节点必然会是等待时间最长的节点,也就是每次调用condition的signal方法是将头节点移动到同步队列中。我们来通过看源码的方式来看这样的猜想是不是对的,signal方法源码为:

    public final void signal() {
        //1. 先检测当前线程是否已经获取lock
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        //2. 获取等待队列中第一个节点,之后的操作都是针对这个节点
    	Node first = firstWaiter;
        if (first != null)
            doSignal(first);
    }
    

    signal方法首先会检测当前线程是否已经获取lock,如果没有获取lock会直接抛出异常,如果获取的话再得到等待队列的头指针引用的节点,之后的操作的doSignal方法也是基于该节点。下面我们来看看doSignal方法做了些什么事情,doSignal方法源码为:

    private void doSignal(Node first) {
        do {
            if ( (firstWaiter = first.nextWaiter) == null)
                lastWaiter = null;
    		//1. 将头结点从等待队列中移除
            first.nextWaiter = null;
    		//2. while中transferForSignal方法对头结点做真正的处理
        } while (!transferForSignal(first) &&
                 (first = firstWaiter) != null);
    }
    

    具体逻辑请看注释,真正对头节点做处理的逻辑在transferForSignal放,该方法源码为:

    final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
    	//1. 更新状态为0
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
    
        /*
         * Splice onto queue and try to set waitStatus of predecessor to
         * indicate that thread is (probably) waiting. If cancelled or
         * attempt to set waitStatus fails, wake up to resync (in which
         * case the waitStatus can be transiently and harmlessly wrong).
         */
    	//2.将该节点移入到同步队列中去
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }
    

    关键逻辑请看注释,这段代码主要做了两件事情1.将头结点的状态更改为CONDITION;2.调用enq方法,将该节点尾插入到同步队列中,关于enq方法请看AQS的底层实现这篇文章。现在我们可以得出结论:调用condition的signal的前提条件是当前线程已经获取了lock,该方法会使得等待队列中的头节点即等待时间最长的那个节点移入到同步队列,而移入到同步队列后才有机会使得等待线程被唤醒,即从await方法中的LockSupport.park(this)方法中返回,从而才有机会使得调用await方法的线程成功退出。signal执行示意图如下图:

    signal执行示意图

    signalAll

    sigllAll与sigal方法的区别体现在doSignalAll方法上,前面我们已经知道doSignal方法只会对等待队列的头节点进行操作,而doSignalAll的源码为:

    private void doSignalAll(Node first) {
        lastWaiter = firstWaiter = null;
        do {
            Node next = first.nextWaiter;
            first.nextWaiter = null;
            transferForSignal(first);
            first = next;
        } while (first != null);
    }
    

    该方法只不过时间等待队列中的每一个节点都移入到同步队列中,即“通知”当前调用condition.await()方法的每一个线程。

    await与signal/signalAll的结合思考

    文章开篇提到等待/通知机制,通过使用condition提供的await和signal/signalAll方法就可以实现这种机制,而这种机制能够解决最经典的问题就是“生产者与消费者问题”,关于“生产者消费者问题”之后会用单独的一篇文章进行讲解,这也是面试的高频考点。await和signal和signalAll方法就像一个开关控制着线程A(等待方)和线程B(通知方)。它们之间的关系可以用下面一个图来表现得更加贴切:

    在这里插入图片描述

    如图,线程awaitThread先通过lock.lock()方法获取锁成功后调用了condition.await方法进入等待队列,而另一个线程signalThread通过lock.lock()方法获取锁成功后调用了condition.signal或者signalAll方法,使得线程awaitThread能够有机会移入到同步队列中,当其他线程释放lock后使得线程awaitThread能够有机会获取lock,从而使得线程awaitThread能够从await方法中退出执行后续操作。如果awaitThread获取lock失败会直接进入到同步队列

    一个例子

    我们用一个很简单的例子说说condition的用法:

    public class AwaitSignal {
        private static ReentrantLock lock = new ReentrantLock();
        private static Condition condition = lock.newCondition();
        private static volatile boolean flag = false;
    
        public static void main(String[] args) {
            Thread waiter = new Thread(new waiter());
            waiter.start();
            Thread signaler = new Thread(new signaler());
            signaler.start();
        }
    
        static class waiter implements Runnable {
    
            @Override
            public void run() {
                lock.lock();
                try {
                    while (!flag) {
                        System.out.println(Thread.currentThread().getName() + "当前条件不满足等待");
                        try {
                            condition.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println(Thread.currentThread().getName() + "接收到通知条件满足");
                } finally {
                    lock.unlock();
                }
            }
        }
    
        static class signaler implements Runnable {
    
            @Override
            public void run() {
                lock.lock();
                try {
                    flag = true;
                    condition.signalAll();
                } finally {
                    lock.unlock();
                }
            }
        }
    }
    

    输出结果为:

    Thread-0当前条件不满足等待
    Thread-0接收到通知,条件满足
    

    开启了两个线程waiter和signaler,waiter线程开始执行的时候由于条件不满足,执行condition.await方法使该线程进入等待状态同时释放锁,signaler线程获取到锁之后更改条件,并通知所有的等待线程后释放锁。这时,waiter线程获取到锁,并由于signaler线程更改了条件此时相对于waiter来说条件满足,继续执行。

    参考文献 《Java并发编程的艺术》

    展开全文
  • java中等待通知机制(wait/notify)

    千次阅读 2017-02-11 11:24:00
    等待/通知的相关方法 方法名称 描述 notify() 通知一个在对象上等待的线程,由WAITING状态变为BLOCKING状态,从等待队列移动到同步队列,等待CPU调度获取该对象的锁,当该线程获取到了对象的锁后,该线程从wait()...

    参考书籍:

    java并发编程的艺术

    等待/通知的相关方法

    方法名称描述
    notify()通知一个在对象上等待的线程,由WAITING状态变为BLOCKING状态,从等待队列移动到同步队列,等待CPU调度获取该对象的锁,当该线程获取到了对象的锁后,该线程从wait()方法返回
    notifyAll()通知所有等待在该对象上的线程,由WAITING状态变为BLOCKING状态,等待CPU调度获取该对象的锁
    wait()调用该方法的线程进入WAITING状态,并将当前线程放置到对象的等待队列,只有等待另外线程的通知或被中断才会返回,需要注意,调用wait()方法后,会释放对象的锁
    wait(long)超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回
    wait(long,int)对于超时时间更细力度的控制,可以达到纳秒


    注意:以上方法必须首先获得该对象的锁后才能调用,否则会抛出IllegalMonitorStateException

    Throws: 
    IllegalMonitorStateException - if the current thread is not the owner of this object's monitor. 
    

    等待通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。

    public class WaitNotify{
        static boolean flag = true;//不需要为volatile,因为对于flag的操作均在synchronized锁的保护下进行,可以保证flag的内存可见性
        static Object lock = new Object();
    
        public static void main(String args[]) throws Exception{
            Thread waitThread = new Thread(new Wait(),"WaitThread");
            waitThread.start();
            TimeUnit.SECONDS.sleep(1);//1 second  -> package java.util.cocurrent
            Thread notifyThread = new Thread(new Notify(),"NotifyThread");
            notifyThread.start();
        }
    
        static class Wait implements Runnable{
            public void run(){
            //加锁,拥有lock的monitor
                synchronized(lock){
                //当条件不满足时,继续wait,同时释放了lock的锁
                    while(flag){
                        try{
                            System.out.println("flag is ture, Wait");
                            lock.wait();
                        }catch(InterruptedException e){
                            //除了notify通知,带超时的wait()方法、线程中断机制也能唤醒此线程
                        }
                    }
                    System.out.println("flag is false,complete");
                }
            }
        }
    
        static class Notify implements Runnable{
            public void run(){
                synchronized(lock){
                    //获取lock的锁,然后进行通知,通知时不会释放lock的锁,直到当前线程释放了lock,调用了notifyAll,并且WaitThread获得了锁之后,wait线程才能从wait()方法返回
                    System.out.println("Notify get lock ,begin notify");
                    lock.notifyAll();
                    flag = false;
                    TimeUnit.SECONDS.sleep(5);
                }
    
                synchronized(lock){
                    System.out.println("Notify get lock again");
                }
            }
        }
    }
    展开全文
  • Object类是Java中所有类的父类, 在线程间实现通信的往往会应用到Object的几个方法: wait(),wait(long timeout),wait(long timeout, int nanos)与notify(),notifyAll() 实现等待/通知机制,同样的, 在Java Lock...
  • 等待/通知机制

    千次阅读 2019-05-06 17:45:59
    一、等待/通知机制 1.1 wait/notify机制的由来 如果某一线程通过while轮询机制来检测某一条件,轮询时间间隔很小,会更浪费CPU资源;如果轮询时间间隔很大,可能会取不到想要的数据,所以就需要一种机制来减少CPU...
  • 多线程的等待/通知机制

    千次阅读 2017-01-19 18:06:39
    等待/通知机制,是指一个线程A调用了对象O的wiat方法进入等待状态,而另一个线程B调用了对象O的notify或者notifyAll方法,线程A收到通知后从对象O的wait方法返回,进而执行后续操作。等待/通知的实例分析在下面的...
  • Java中的等待/通知机制

    千次阅读 2018-06-12 20:37:14
    Java中的等待/通知机制
  • Java中的等待通知/机制

    千次阅读 2018-05-27 11:28:21
    等待/通知机制是任意的java对象都具备的。因为这些方法都定义在所有对象的超类java.lang.Object方法上。等待/通知 的相关方法wait()方法:调用该方法的线程进入waiting状态,只有等待另外线程的通知或被中断才会返回...
  • 1. 等待/通知机制等待/通知机制,是指WaitThread首先获取了对象的锁,然后调用对象的wait()方法,从而放弃了锁进入了对象的等待队列中,进入等待状态。NotifyThread随后获取了对象的锁,并调用对象的notify()或...
  • 详解Condition的await和signal等待/通知机制

    万次阅读 多人点赞 2018-05-06 14:54:40
    任何一个java对象都天然继承于Object类,在线程间实现通信的往往会应用到Object的几个方法,比如wait(),wait(long timeout),wait(long timeout, int nanos)与notify(),notifyAll()几个方法实现等待/通知机制,同样的...
  • public class ValueObject { public static String value=""; } /** * 生产者 */ public class P { private String lock; public P(String lock){ this.lock = lock;... String value = System.currentTimeMillis...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 206,048
精华内容 82,419
关键字:

等待通知机制