天天看點

并發程式設計 -- join和sleep

在看并發程式設計這本書時,看到join()方法,似曾相識的感覺,但是一時間又想不出具體的作用,覺得還是寫寫代碼記錄下,加深記憶号

先看一個demo

public class JoinAndSleepDemo {

   private static Thread thread1 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("子線1程在執行----------1111111111");
        }
    });

   private static Thread thread2 = new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("子線程2在執行----------2222222222");
        }
    });

    public static void main(String[] args) throws InterruptedException {
        //主線程
        System.out.println("---------主線程------"+Thread.currentThread().getName());

        System.out.println("子線程執行");
        //--------子線程執行
        thread1.start();
       // thread1.join();
        thread2.start();
        //thread2.join();
        //主線程希望等待子線程執行完再執行
        System.out.println("主線程還在執行");
    }

}      

現在先不給出正确的結果,先猜想這段代碼的輸出會是什麼樣,注意,join方法都被注釋掉

猜想一:

---------主線程------main
子線程執行
子線1程在執行----------1111111111
子線程2在執行----------2222222222
主線程還在執行      

顯然,這種猜想跟程式執行順序差不多,思路基本上可以确定是,主線程 – 子線程 – 主線程的順序,也就是主線程是在子線程執行完時還在執行,主線程執行時間最長

猜想二:

---------主線程------main
子線程執行
主線程還在執行
子線1程在執行----------1111111111
子線程2在執行----------2222222222      

這種猜想就是主線程的執行最早結束,不等待子線程執行完就已經結束了

實際上正确的結果也就是猜想二,對于這種情況,如果有時候我們在一些業務場景中主線程希望等待子線程執行完成,并且擷取子線程執行結果,那麼我們該如何處理呢?

join()

還是上面那個demo,這次我把join()方法取消注釋

public static void main(String[] args) throws InterruptedException {
        //主線程
        System.out.println("---------主線程------"+Thread.currentThread().getName());

        System.out.println("子線程執行");
        //--------子線程執行
        thread1.start();
        thread1.join();
        thread2.start();
        thread2.join();
        //主線程希望等待子線程執行完再執行
        System.out.println("主線程還在執行");
    }      

那麼會得到什麼樣的結果呢?

---------主線程------main
子線程執行
子線1程在執行----------1111111111
子線程2在執行----------2222222222
主線程還在執行      

此時這種情況,主線程就會等待執行了join方法的兩個子線程執行完。

表面看似這樣,可實際原理是什麼呢?

我們先觀察線程狀态在執行join方法前後會方法什麼變化?

public static void main(String[] args) throws InterruptedException {
        //主線程
        System.out.println("主線程狀态---------->"+Thread.currentThread().getState());
        System.out.println("---------主線程------"+Thread.currentThread().getName());
        System.out.println("主線程狀态---------->"+Thread.currentThread().getState());
        System.out.println("子線程執行");
        //--------子線程執行
        thread1.start();
        System.out.println(Thread.currentThread().getName()+"線程狀态---------->"+Thread.currentThread().getState());
        System.out.println("線程111111111狀态-----------》"+thread1.getState());
        thread1.join();
        System.out.println("線程111111111狀态-----------》"+thread1.getState());
        System.out.println(Thread.currentThread().getName()+"線程狀态---------->"+Thread.currentThread().getState());
        thread2.start();
        System.out.println("線程222222222狀态-----------》"+thread2.getState());
        thread2.join();
        System.out.println("線程222222222狀态-----------》"+thread2.getState());
        System.out.println(Thread.currentThread().getName()+"線程狀态---------->"+Thread.currentThread().getState());
        //主線程希望等待子線程執行完再執行
        System.out.println("主線程還在執行");
        System.out.println(Thread.currentThread().getName()+"線程狀态---------->"+Thread.currentThread().getState());
    }      

變化結果:

主線程狀态---------->RUNNABLE
---------主線程------main
主線程狀态---------->RUNNABLE
子線程執行
main線程狀态---------->RUNNABLE
線程111111111狀态-----------》RUNNABLE
子線1程在執行----------1111111111
線程111111111狀态-----------》TERMINATED
main線程狀态---------->RUNNABLE
線程222222222狀态-----------》RUNNABLE
子線程2在執行----------2222222222
線程222222222狀态-----------》TERMINATED
main線程狀态---------->RUNNABLE
主線程還在執行
main線程狀态---------->RUNNABLE      

可以看到子線程在執行了join方法後線程狀态由RUNNABLE變為TERMINATED

主線程一直都是RUNNABLE

是以,我們大概可以得到一個結論就是:

子線程thread1在調用join()方法後,thread1正常執行run,但是目前thread1所在的main線程就會被無限期阻塞,隻有當thread1執行完,main線程才能恢複正常執行

是以,join() 方法使得調用該方法的那段代碼所在的線程暫時阻塞

sleep()

sleep方法是和join()方法比較相似的方法

先看一個demo

@Slf4j
public class SleepDemo {

    static class ThreadA extends Thread{

        @Override
        public void run() {
            synchronized (this){
                log.info("------>"+this.getName());
                log.info("------>"+Thread.currentThread().getName()+"begin:"+System.currentTimeMillis());

                log.info("------>"+Thread.currentThread().getName()+"end:"+System.currentTimeMillis());
            }
        }
    }

    static class ThreadB extends Thread{

        private ThreadA threadA;

        public ThreadB(ThreadA threadA) {
            this.threadA = threadA;
        }

        @Override
        public void run() {
            synchronized (threadA){
                log.info("------>"+Thread.currentThread().getName()+"begin:"+System.currentTimeMillis());
                try {
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.info("------>"+Thread.currentThread().getName()+"end:"+System.currentTimeMillis());
            }
        }
    }

    public static void main(String[] args) {

        ThreadA threadA = new ThreadA();
        threadA.setName("AAAAAAAAAAAA");
        ThreadB threadB = new ThreadB(threadA);
        threadB.setName("BBBBBBBBBBBB");

        threadB.start();
        threadA.start();
    }
}      

結果:

22:17:19.259 [BBBBBBBBBBBB] INFO com.kevin.demo.base_of_cconcurrency.SleepDemo - ------>BBBBBBBBBBBBbegin:1562595439257
22:17:23.263 [BBBBBBBBBBBB] INFO com.kevin.demo.base_of_cconcurrency.SleepDemo - ------>BBBBBBBBBBBBend:1562595443263
22:17:23.263 [AAAAAAAAAAAA] INFO com.kevin.demo.base_of_cconcurrency.SleepDemo - ------>AAAAAAAAAAAA
22:17:23.263 [AAAAAAAAAAAA] INFO com.kevin.demo.base_of_cconcurrency.SleepDemo - ------>AAAAAAAAAAAAbegin:1562595443263
22:17:23.263 [AAAAAAAAAAAA] INFO com.kevin.demo.base_of_cconcurrency.SleepDemo - ------>AAAAAAAAAAAAend:1562595443263      

可以看到,先執行的線程BBBBBBBBB的run方法,執行線程BBBBBBBB的run方法時,對線程AAAAAA對象加鎖,隻有一個線程能進入該方法執行,線程AAAAAAAAAA執行run方法時,因為線程AAAAAAAAAAA也對自己加鎖,線程BBBBBBBBB還沒有釋放鎖,是以線程AAAAAAAAAA不能執行其run方法中的同步塊,需要等線程BBBBBBBBBB執行完成釋放鎖才能執行

是以,可以得到一個結論:

即 Thread.sleep(long) 方法是不釋放對象鎖的,并且使目前線程進入阻塞狀态

join和sleep的差別

探究下和join()方法的差別,從上文中可以得到,join方法是目前線程執行join方法之後,會阻塞其所在的線程,等到目前執行join方法的線程執行完成,才會恢複正常執行狀态

在上文中,我們可以得到,sleep()可以使線程休眠一段時間,使目前線程進入阻塞狀态

通過一個demo,比較兩個方法的差異

@Slf4j
public class SleepDemo {

    static class ThreadA extends Thread{

        @Override
        public void run() {
            synchronized (this){
                log.info("------>"+this.getName());
                log.info("------>"+Thread.currentThread().getName()+"begin:"+System.currentTimeMillis());

                log.info("------>"+Thread.currentThread().getName()+"end:"+System.currentTimeMillis());
            }
        }
    }

    static class ThreadB extends Thread{

        private ThreadA threadA;

        public ThreadB(ThreadA threadA) {
            this.threadA = threadA;
        }

        @Override
        public void run() {
            synchronized (threadA){
                log.info("------>"+Thread.currentThread().getName()+"begin:"+System.currentTimeMillis());
                try {
                    log.info("-------->before: "+threadA.isAlive());
                    threadA.join();
                    log.info("---------->after: "+threadA.isAlive());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.info("------>"+Thread.currentThread().getName()+"end:"+System.currentTimeMillis());
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {

        ThreadA threadA = new ThreadA();
        threadA.setName("AAAAAAAAAAAA");
        ThreadB threadB = new ThreadB(threadA);
        threadB.setName("BBBBBBBBBBBB");

        threadB.start();
        threadA.start();
    }
}      

和上文中的demo,隻是做了些少改變

22:45:59.359 [BBBBBBBBBBBB] INFO com.kevin.demo.base_of_cconcurrency.SleepDemo - ------>BBBBBBBBBBBBbegin:1562597159357
22:45:59.363 [BBBBBBBBBBBB] INFO com.kevin.demo.base_of_cconcurrency.SleepDemo - -------->before: true
22:45:59.363 [AAAAAAAAAAAA] INFO com.kevin.demo.base_of_cconcurrency.SleepDemo - ------>AAAAAAAAAAAA
22:45:59.363 [AAAAAAAAAAAA] INFO com.kevin.demo.base_of_cconcurrency.SleepDemo - ------>AAAAAAAAAAAAbegin:1562597159363
22:45:59.363 [AAAAAAAAAAAA] INFO com.kevin.demo.base_of_cconcurrency.SleepDemo - ------>AAAAAAAAAAAAend:1562597159363
22:45:59.364 [BBBBBBBBBBBB] INFO com.kevin.demo.base_of_cconcurrency.SleepDemo - ---------->after: false
22:45:59.364 [BBBBBBBBBBBB] INFO com.kevin.demo.base_of_cconcurrency.SleepDemo - ------>BBBBBBBBBBBBend:1562597159364      

總結

join和sleep差別

  • sleep(long)方法在睡眠時不釋放對象鎖
  • join(long)方法在等待的過程中釋放對象鎖
  • sleep是讓調用線程進入阻塞狀态

sleep和wait差別

  • sleep是Thread類的靜态方法,wait是object的方法
  • sleep()方法導緻了程式暫停執行指定的時間,讓出cpu該其他線程,但是他的監控狀态依然保持者,當指定的時間到了又會自動恢複運作狀态。在調用sleep()方法的過程中,線程不會釋放對象鎖。

    而當調用wait()方法的時候,線程會放棄對象鎖,進入等待此對象的等待鎖定池,隻有針對此對象調用notify()方法後本線程才進入對象鎖定池準備

  • sleep方法需要抛異常,wait方法不需要
  • sleep方法可以在任何地方使用,wait方法隻能在同步方法和同步代碼塊中使用