zoukankan      html  css  js  c++  java
  • Object的wait/notify/notifyAll&&Thread的sleep/yield/join/holdsLock

    一、wait/notify/notifyAll都是Object类的实例方法

    1、wait方法:阻塞当前线程等待notify/notifyAll方法的唤醒,或等待超时后自动唤醒。

    wait等待其实是对象monitor

    JDK中提供了三个重载方法

      (1)void wait()方法的作用是将当前运行的线程挂起(即让其进入阻塞状态),不再占据CPU,直到notify或notifyAll方法来唤醒线程.

      (2)void wait(long timeout),该方法与wait()方法类似,唯一的区别就是在指定时间内,如果没有notify或notifAll方法的唤醒,也会自动唤醒。

      (3)void wait(long timeout,long nanos),本意在于更精确的控制调度时间,不过从目前版本来看,该方法貌似没有完整的实现该功能。

    注意:wait(0);// timeout 为零时,则不考虑实际时间,在获得通知前该线程将一直等待;和wait()是一样的含义。实际wait()的实现就一句代码:wait(0)

    我们写一段代码实现:程序执行以后,暂停一秒,然后再执行。

    package com.paddx.test.concurrent;
    
    public class WaitTest {
    
        public void testWait(){
            System.out.println("Start-----");
            try {
                wait(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("End-------");
        }
    
        public static void main(String[] args) {
            final WaitTest test = new WaitTest();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.testWait();
                }
            }).start();
        }
    }

    运行上述代码,查看结果:

    Start-----

    Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
        at java.lang.Object.wait(Native Method)
        at com.paddx.test.concurrent.WaitTest.testWait(WaitTest.java:8)
        at com.paddx.test.concurrent.WaitTest$1.run(WaitTest.java:20)
        at java.lang.Thread.run(Thread.java:745)
     
    这段程序并没有按我们的预期输出相应结果,而是抛出了一个异常。大家可能会觉得奇怪为什么会抛出异常?而抛出的IllegalMonitorStateException异常又是什么?我们可以看一下JDK中对IllegalMonitorStateException的描述:

    Thrown to indicate that a thread has attempted to wait on an object's monitor or to notify other threads waiting on an object's monitor without owning the specified monitor.

    这句话的意思大概就是:线程 试图等待对象的监视器 或者 试图通知其他正在等待对象监视器 的线程,但本身没有对应的监视器的所有权

    这个问题在《synchronized底层实现原理》一文中有提到过,wait方法是一个本地方法,其底层是通过一个叫做监视器锁的对象来完成的。所以上面之所以会抛出异常,是因为在调用wait方式时没有获取到monitor对象的所有权。

    那如何获取monitor对象所有权?Java中只能通过synchronized关键字来完成,修改上述代码,增加Synchronized关键字:

    package com.paddx.test.concurrent;
    
    public class WaitTest {
    
        public synchronized void testWait(){//增加Synchronized关键字
            System.out.println("Start-----");
            try {
                wait(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("End-------");
        }
    
        public static void main(String[] args) {
            final WaitTest test = new WaitTest();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    test.testWait();
                }
            }).start();
        }
    }

    现在再运行上述代码,就能看到预期的效果了:

    Start-----

    End-------
    于是通过wait(long timeout)方法在单线程中,便实现了与Thread.sleep(long millis)一样的效果
     
    通过这个例子,我们明白:wait方法的使用必须在同步的范围内,即执行时当前线程必须拥有moniter对象的所有权,否则就会抛出IllegalMonitorStateException异常。同理,notify/notifyAll方法执行时有同样的要求

    2、notify/notifyAll方法

    (1)void notify() :Wakes up a single thread that is waiting on this object's monitor.  有多个线程在等待同一个object的monitor时,仅仅唤醒其中的一个线程(随机选择, 或者说是竞争到锁的那一个)

    (2)void notifyAll():Wakes up all threads that are waiting on this object's monitor.  有多个线程在等待同一个object的monitor时,会唤醒全部的这些线程(按照竞争到锁的顺序逐个唤醒)

    wait/notify/notifyAll是通过对象的monitor对象来实现的,只要在同一对象上去调用notify/notifyAll方法,就可以唤醒对应对象monitor上等待的线程了


    看下面的例子来理解这两者的差别:

    package com.paddx.test.concurrent;
    
    public class NotifyTest {
        public synchronized void testWait(){
            System.out.println(Thread.currentThread().getName() +" Start-----");
            try {
                wait(0);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() +" End-------");
        }
    
        public static void main(String[] args) throws InterruptedException {
            final NotifyTest test = new NotifyTest();
            for(int i=0;i<5;i++) {
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        test.testWait();
                    }
                }).start();
            }
    
            Thread.sleep(1000);
            synchronized (test) {
                test.notify();
            }
            Thread.sleep(3000);
            System.out.println("-----------分割线-------------");
            
            synchronized (test) {
                test.notifyAll();
            }
        }
    }

    输出结果如下:

    Thread-0 Start-----

    Thread-1 Start-----
    Thread-2 Start-----
    Thread-3 Start-----
    Thread-4 Start-----
    Thread-0 End-------
    -----------分割线-------------
    Thread-4 End-------
    Thread-3 End-------
    Thread-2 End-------
    Thread-1 End-------
    从结果可以看出:调用notify方法时只有线程Thread-0被唤醒,但是调用notifyAll时,所有的线程都被唤醒了。

    最后,有两点点需要注意:

      (1)调用wait方法后,线程是会释放对monitor对象的所有权

      (2)一个通过wait方法阻塞的线程,必须同时满足以下两个条件才能被真正执行:

    •     线程需要被唤醒(超时唤醒或调用notify/notifyAll)
    •     线程唤醒后需要竞争到锁(monitor)

    注意: notifyAll时,如果多个线程在wait,此时多个线程需要对锁进行竞争;竞争到锁的线程获得执行,等该线程释放锁后,剩下的线程再次竞争;如此重复,直到上一轮wait的线程都得到一次锁得以执行,否则一旦有线程释放了锁,未曾得到过锁的线程还会得到通知

    二、Thread类的sleep/yield/join方法:sleep和yield是静态方法,join是实例方法

    sleep/join的底层实现也使用了synchronized /wait等机制或方法

    1、sleep

    先看一下实现源码:

    public static void sleep(long millis) throws InterruptedException {
            Thread.sleep(millis, 0);
    }
    
    public static void sleep(long millis, int nanos) throws InterruptedException {
            if (millis < 0) {
                throw new IllegalArgumentException("millis < 0: " + millis);
            }
            if (nanos < 0) {
                throw new IllegalArgumentException("nanos < 0: " + nanos);
            }
            if (nanos > 999999) {
                throw new IllegalArgumentException("nanos > 999999: " + nanos);
            }
    
            // The JLS 3rd edition, section 17.9 says: "...sleep for zero
            // time...need not have observable effects."
            if (millis == 0 && nanos == 0) {
                // ...but we still have to handle being interrupted.
                if (Thread.interrupted()) {
                  throw new InterruptedException();
                }
                return;
            }
    
            long start = System.nanoTime();
            long duration = (millis * NANOS_PER_MILLI) + nanos;
    
            Object lock = currentThread().lock;
    
            // Wait may return early, so loop until sleep duration passes.
            synchronized (lock) {//还是使用了锁
                while (true) {
                    sleep(lock, millis, nanos);
    
                    long now = System.nanoTime();
                    long elapsed = now - start;
    
                    if (elapsed >= duration) {
                        break;
                    }
    
                    duration -= elapsed;
                    start = now;
                    millis = duration / NANOS_PER_MILLI;
                    nanos = (int) (duration % NANOS_PER_MILLI);
                }
            }
        }
    
    @FastNative
    private static native void sleep(Object lock, long millis, int nanos)
            throws InterruptedException;

    sleep方法的作用是让当前线程暂停指定的时间(毫秒)。唯一需要注意的是其与wait方法的区别。

    1)wait方法依赖于同步,而sleep方法可以直接调用。

    2)sleep方法只是暂时让出CPU的执行权,并不释放锁。而wait方法则需要释放锁。(看下面的例子理解)

    第2)个区别可以理解 调用 Thread.sleep(0) 的作用:进行一次CPU使用权的重新争夺

    package com.paddx.test.concurrent;
    
    public class SleepTest {
        public synchronized void sleepMethod(){
            System.out.println("Sleep start-----");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("Sleep end-----");
        }
    
        public synchronized void waitMethod(){
            System.out.println("Wait start-----");
            synchronized (this){
                try {
                    wait(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("Wait end-----");
        }
    
        public static void main(String[] args) {
            final SleepTest test1 = new SleepTest();
    
            for(int i = 0;i<3;i++){
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        test1.sleepMethod();
                    }
                }).start();
            }
    
    
            try {
                Thread.sleep(10000);//暂停十秒,等上面程序执行完成
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("-----分割线-----");
    
            final SleepTest test2 = new SleepTest();
    
            for(int i = 0;i<3;i++){
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        test2.waitMethod();
                    }
                }).start();
            }
    
        }
    }

    执行结果:

    Sleep start-----

    Sleep end-----
    Sleep start-----
    Sleep end-----
    Sleep start-----
    Sleep end-----
    -----分割线-----
    Wait start-----
    Wait start-----
    Wait start-----
    Wait end-----
    Wait end-----
    Wait end-----
     
    这个结果的区别很明显,通过sleep方法实现的暂停,程序是顺序进入同步块的,只有当上一个线程执行完成的时候,下一个线程才能进入同步方法,sleep暂停期间一直持有monitor对象锁,其他线程是不能进入的。
    而wait方法则不同,当调用wait方法后,当前线程会释放持有的monitor对象锁,因此,其他线程还可以进入到同步方法,线程被唤醒后,需要竞争锁获取到锁之后再继续执行
     
     
    2、yield方法
    yield方法的作用是暂停当前线程,以便其他线程有机会执行,不过不能指定暂停的时间,并且也不能保证当前线程马上停止yield方法只是将Running状态转变为Runnable状态。我们还是通过一个例子来演示其使用:
    package com.paddx.test.concurrent;
    
    public class YieldTest implements Runnable {
        @Override
        public void run() {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            for(int i=0;i<5;i++){
                System.out.println(Thread.currentThread().getName() + ": " + i);
                Thread.yield();
            }
        }
    
        public static void main(String[] args) {
            YieldTest runn = new YieldTest();
            Thread t1 = new Thread(runn,"FirstThread");
            Thread t2 = new Thread(runn,"SecondThread");
    
            t1.start();
            t2.start();
    
        }
    }

    运行结果如下:

    FirstThread: 0

    SecondThread: 0
    FirstThread: 1
    SecondThread: 1
    FirstThread: 2
    SecondThread: 2
    FirstThread: 3
    SecondThread: 3
    FirstThread: 4
    SecondThread: 4
    这个例子就是通过yield方法来实现两个线程的交替执行。不过请注意:这种交替并不一定能得到保证,源码中也对这个问题也进行了说明。

    源码中的说明主要说了三个问题:

    •   调度器可能会忽略该方法。
    •   使用的时候要仔细分析和测试,确保能达到预期的效果。
    •   很少有场景要用到该方法,主要使用的地方是调试和测试。 
    3、join方法
    首先贴一下源码实现:
    public final void join(long millis, int nanos)
        throws InterruptedException {
            synchronized(lock) {
            if (millis < 0) {
                throw new IllegalArgumentException("timeout value is negative");
            }
    
            if (nanos < 0 || nanos > 999999) {
                throw new IllegalArgumentException(
                                    "nanosecond timeout value out of range");
            }
    
            if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
                millis++;
            }
    
            join(millis);
            }
    }
    
    public final void join() throws InterruptedException {
            join(0);
    }
    
    public final void join(long millis) throws InterruptedException {
            synchronized(lock) {//使用了锁
            long base = System.currentTimeMillis();
            long now = 0;
    
            if (millis < 0) {
                throw new IllegalArgumentException("timeout value is negative");
            }
    
            if (millis == 0) {
                while (isAlive()) {
                    lock.wait(0);//调用了wait
                }
            } else {
                while (isAlive()) {
                    long delay = millis - now;
                    if (delay <= 0) {
                        break;
                    }
                    lock.wait(delay);
                    now = System.currentTimeMillis() - base;
                }
            }
            }
    }

    void join():Waits for this thread to die.

    void join(long millis):Waits at most millis milliseconds for this thread to die.
    void join(long millis, int nanos):Waits at most millis milliseconds plus nanos nanoseconds for this thread to die.
     
    注意:join(0);// millis 为零时,则不考虑实际时间,在该线程执行结束前将一直等待;和join()是一样的含义。实际join()的实现就一句代码:join(0)
     
    join方法的作用是父线程等待子线程执行完成后再执行;换句话说就是将异步执行的线程合并为同步的线程:把thread实例对应的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程

    比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。

    t.join();      //调用join方法,等待线程t执行完毕
    t.join(1000);  //等待 t 线程,等待时间是1000毫秒

    我们可以看上述join方法的源码,这样更容易理解:
    重点关注一下join(long millis)方法的实现,可以看出join方法就是通过wait方法来将线程的阻塞,如果join的线程还在执行,则将当前线程阻塞起来,直到join的线程执行完成,当前线程才能执行。不过有一点需要注意,这里的join只调用了wait方法,却没有对应的notify方法,原因是Thread的start方法中做了相应的处理,所以当join的线程执行完成以后,会自动唤醒主线程继续往下执行。

    下面我们通过一个例子来演示join方法的作用:

     (1)不使用join方法:

    package com.paddx.test.concurrent;
    
    public class JoinTest implements Runnable{
        @Override
        public void run() {
    
            try {
                System.out.println(Thread.currentThread().getName() + " start-----");
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + " end------");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            for (int i=0;i<5;i++) {
                Thread test = new Thread(new JoinTest());
                test.start();
            }
    
            System.out.println("Finished~~~");
        }
    }

    执行结果如下:

    Thread-0 start-----

    Thread-1 start-----
    Thread-2 start-----
    Thread-3 start-----
    Finished~~~
    Thread-4 start-----
    Thread-2 end------
    Thread-4 end------
    Thread-1 end------
    Thread-0 end------
    Thread-3 end------

    (2)使用join方法:

    package com.paddx.test.concurrent;
    
    public class JoinTest implements Runnable{
        @Override
        public void run() {
    
            try {
                System.out.println(Thread.currentThread().getName() + " start-----");
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() + " end------");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    
        public static void main(String[] args) {
            for (int i=0;i<5;i++) {
                Thread test = new Thread(new JoinTest());
                test.start();
                try {
                    test.join(); //调用join方法
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            System.out.println("Finished~~~");
        }
    }

    执行结果如下:

    Thread-0 start-----

    Thread-0 end------
    Thread-1 start-----
    Thread-1 end------
    Thread-2 start-----
    Thread-2 end------
    Thread-3 start-----
    Thread-3 end------
    Thread-4 start-----
    Thread-4 end------
    Finished~~~
    对比两段代码的执行结果很容易发现,在没有使用join方法之间,线程是并发执行的,而使用join方法后,所有线程是顺序执行的。
     
    参考 http://www.cnblogs.com/paddix/p/5381958.html
     
    4.holdsLock Thread类的静态方法
    public static boolean holdsLock(Object obj)  //判断当前线程是否拥有obj的锁(Monitor)
  • 相关阅读:
    docker 部署 nginx+php+mysql
    jquery-weui picker组件实现只选择年月
    ios端微信浏览器禁止上下滑动
    mysql 统计连续天数
    mysql 省市数据
    php 获取毫秒时间戳
    create-react-app 打包后文件路径问题
    php nginx 获取header信息
    ubuntu或者debian安装php-gd扩展错误
    php xml字符串转数组
  • 原文地址:https://www.cnblogs.com/genggeng/p/9888043.html
Copyright © 2011-2022 走看看