在看并發程式設計這本書時,看到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方法隻能在同步方法和同步代碼塊中使用