小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

多線程的執(zhí)行流程以及各個狀態(tài)描述

 忠波irlphwt1ng 2020-02-24

在編程工作中,我們經(jīng)常會用到或聽到多線程三個字,多線程編程的好處就是可以讓多個任務(wù)進(jìn)行并發(fā),從而更加充分利用CPU,減少CPU的無效等待時間。

多線程的執(zhí)行流程圖如下:

接下來我們會映照上圖介紹多線程執(zhí)行過程中經(jīng)歷的五種狀態(tài):

1. 新建狀態(tài):

新建狀態(tài)就是我們通過new關(guān)鍵字實例化出一個線程類的對象時的狀態(tài)。

  1. public class IsAThread extends Thread{
  2. @Override
  3. public void run() {
  4. System.out.println("這是一個線程類");
  5. }
  6. }
  1. public static void main(String[] args) {
  2. // 線程進(jìn)入新建狀態(tài)
  3. IsAThread isAThread = new IsAThread();
  4. }

此時,我們就說 isAThread 這個線程對象進(jìn)入了新建狀態(tài)。

2. 可運行狀態(tài):

當(dāng)我們調(diào)用了新建狀態(tài)下的線程對象的 start() 方法來啟動這個線程,并且線程對象已經(jīng)準(zhǔn)備好了除CPU時間片段之外的所有資源后,該線程對象會被放入“可運行線程池”中等待CPU分配時間片段給自身。在自身獲得CPU的時間片段之后便會執(zhí)行自身 run() 方法中定義的邏輯,示例中的線程對象的 run() 方法是打印了 “這是一個線程類” 這么一句話到控制臺。

  1. public static void main(String[] args) {
  2. // 線程進(jìn)入新建狀態(tài)
  3. IsAThread isAThread = new IsAThread();
  4. // 線程進(jìn)入可運行狀態(tài)
  5. isAThread.start();
  6. }

3. 運行狀態(tài):

運行狀態(tài)的線程在分配到CPU的時間片段之后,便會真正開始執(zhí)行線程對象 run() 方法中定義的邏輯代碼了,示例中的線程對象的 run() 方法是打印了 “這是一個線程類” 這么一句話到控制臺。

1)但是生產(chǎn)環(huán)境中的線程對象的 run() 方法一般不會這么簡單,可能業(yè)務(wù)代碼邏輯復(fù)雜,造成CPU的時間片段所規(guī)定的時長已經(jīng)用完之后,業(yè)務(wù)代碼還沒執(zhí)行完;

2)或者是當(dāng)前線程主動調(diào)用了Thread.yield()方法來讓出自身的CPU時間片段。

  1. public class IsAThread extends Thread{
  2. @Override
  3. public void run() {
  4. // 主動讓出自身獲取到的CPU時間片段給其他線程使用
  5. Thread.yield();
  6. System.out.println("這是一個線程類");
  7. }
  8. }

此時,運行狀態(tài)會轉(zhuǎn)回可運行狀態(tài),等待下一次分配到CPU時間片段之后繼續(xù)執(zhí)行未完成的操作。

4. 阻塞狀態(tài):

阻塞狀態(tài)指的是運行狀態(tài)中的線程因為某種原因主動放棄了自己的CPU時間片段來讓給其他線程使用,可能的阻塞類型及原因有:

4.1 等待阻塞:

線程被調(diào)用了 Object.wait() 方法后會立刻釋放掉自身獲取到的鎖并進(jìn)入“等待池”進(jìn)行等待,等待池中的線程被其他線程調(diào)用了 Object.notify() 或 Object.notifyAll() 方法后會被喚醒從而從“等待池”進(jìn)入到“等鎖池”,“等鎖池”中的線程在重新獲取到鎖之后會轉(zhuǎn)為可運行狀態(tài)。

值得注意的是:wait()和notify()/notifyAll()只能用在被synchronized包含的代碼塊中,而說明中的Object.wait和Object.notify的這個Object實際上是指作為synchronized鎖的對象。

例如:

我們創(chuàng)建兩個線程類,StringBufferThread和StringBufferThread2,這兩個類唯一的不同就是run()方法的實現(xiàn)。

StringBufferThread:

  1. import java.util.concurrent.CountDownLatch;
  2. public class StringBufferThread implements Runnable {
  3. StringBuffer sb;
  4. CountDownLatch countDownLatch;
  5. StringBufferThread(StringBuffer sb, CountDownLatch countDownLatch) {
  6. this.sb = sb;
  7. this.countDownLatch = countDownLatch;
  8. }
  9. @Override
  10. public void run() {
  11. // StringBufferThread這個類作為鎖
  12. synchronized (StringBufferThread.class) {
  13. sb.append("This is StringBufferThread1\n");
  14. countDownLatch.countDown();
  15. }
  16. }
  17. }

StringBufferThread2:

  1. import java.util.concurrent.CountDownLatch;
  2. public class StringBufferThread2 implements Runnable{
  3. StringBuffer sb;
  4. CountDownLatch countDownLatch;
  5. StringBufferThread2(StringBuffer sb, CountDownLatch countDownLatch) {
  6. this.sb = sb;
  7. this.countDownLatch = countDownLatch;
  8. }
  9. @Override
  10. public void run() {
  11. // StringBufferThread這個類作為鎖
  12. synchronized (StringBufferThread.class) {
  13. sb.append("This is StringBufferThread2\n");
  14. countDownLatch.countDown();
  15. }
  16. }
  17. }

main:

  1. public static void main(String[] args) throws InterruptedException {
  2. StringBuffer tipStr = new StringBuffer();
  3. // 使用CountDownLatch保證子線程全部執(zhí)行完成后主線程才打印結(jié)果
  4. CountDownLatch countDownLatch = new CountDownLatch(2);
  5. StringBufferThread stringBufferThread = new StringBufferThread(tipStr, countDownLatch);
  6. StringBufferThread2 stringBufferThread2 = new StringBufferThread2(tipStr, countDownLatch);
  7. Thread thread1 = new Thread(stringBufferThread);
  8. Thread thread2 = new Thread(stringBufferThread2);
  9. thread1.start();
  10. /*
  11. 為了保證先讓thread1執(zhí)行,我們讓thread1執(zhí)行后主線程睡眠5秒鐘再執(zhí)行thread2,
  12. 如果不進(jìn)行睡眠的話我們無法控制CPU分配時間片段,有可能直接就先分配給thread2線程了,
  13. 這樣就會造成thread2先于thread1執(zhí)行
  14. */
  15. Thread.sleep(5000);
  16. thread2.start();
  17. // 調(diào)用countDownLatch.await()保證子線程全部執(zhí)行完后主線程才繼續(xù)執(zhí)行
  18. countDownLatch.await();
  19. System.out.println(tipStr.toString());
  20. }

那么我們先來看一下這種沒使用wait()和notify()的情形下,先后執(zhí)行這兩個線程對象時的結(jié)果:

跟邏輯一樣,先執(zhí)行了stringBufferThread然后執(zhí)行了stringBufferThread2。

接下來,修改StringBufferThread類:

  1. import java.util.concurrent.CountDownLatch;
  2. public class StringBufferThread implements Runnable {
  3. StringBuffer sb;
  4. CountDownLatch countDownLatch;
  5. StringBufferThread(StringBuffer sb, CountDownLatch countDownLatch) {
  6. this.sb = sb;
  7. this.countDownLatch = countDownLatch;
  8. }
  9. @Override
  10. public void run() {
  11. // StringBufferThread這個類作為鎖
  12. synchronized (StringBufferThread.class) {
  13. try {
  14. /*
  15. 在將字符串追加到StringBuffer前,調(diào)用鎖對象StringBufferThread這個類的wait(),
  16. 來使本子線程進(jìn)行休眠
  17. */
  18. StringBufferThread.class.wait();
  19. } catch (InterruptedException e) {
  20. e.printStackTrace();
  21. }
  22. sb.append("This is StringBufferThread1\n");
  23. countDownLatch.countDown();
  24. }
  25. }
  26. }

為StringBufferThread類中的run方法中,將字符串"This is StringBufferThread1\n"加入StringBuffer對象之前,加入wait()方法來進(jìn)行等待,注意,wait()方法會立刻釋放掉自身的鎖后,也就是其他爭取到鎖的線程可以運行被這個synchronized保護的代碼塊了。

隨后,我們修改StringBufferThread2:

  1. import java.util.concurrent.CountDownLatch;
  2. public class StringBufferThread2 implements Runnable{
  3. StringBuffer sb;
  4. CountDownLatch countDownLatch;
  5. StringBufferThread2(StringBuffer sb, CountDownLatch countDownLatch) {
  6. this.sb = sb;
  7. this.countDownLatch = countDownLatch;
  8. }
  9. @Override
  10. public void run() {
  11. // StringBufferThread這個類作為鎖
  12. synchronized (StringBufferThread.class) {
  13. sb.append("This is StringBufferThread2\n");
  14. /*
  15. 在將字符串追加到StringBuffer后,調(diào)用鎖對象StringBufferThread這個類的notify(),
  16. 來喚醒本這個鎖對象的wait()方法等待的子線程,本例中就是main方法中的stringBufferThread這個子線程
  17. */
  18. StringBufferThread.class.notify();
  19. countDownLatch.countDown();
  20. }
  21. }
  22. }

也就是在字符串"This is StringBufferThread2\n"追加到StringBuffer之后調(diào)用了 notify() 方法來喚醒被 StringBufferThread.class 這個鎖等待的線程,本例中就是main方法中的stringBufferThread這個子線程,本喚醒的子線程會進(jìn)入等鎖池,等待重新爭取到鎖之后,會繼續(xù)執(zhí)行代碼。

main方法不變,我們來看看執(zhí)行結(jié)果:

與我們預(yù)想的一樣,因為thread1在追加字符串到StringBuffer對象之前調(diào)用了鎖對象的wait(),就立即釋放掉了自身獲取到的鎖并進(jìn)入等待池中了,這時thread2獲取了鎖,將字符串"This is StringBufferThread2\n"首先追加到了StringBuffer對象的開頭,然后調(diào)用鎖對象的notify()方法喚醒了thread1,被喚醒的thread1重新獲取鎖之后,才將自身的字符串"This is StringBufferThread1\n"追加到了StringBuffer對象的末尾。

4.2 同步阻塞:

線程執(zhí)行到了被 synchronized 關(guān)鍵字保護的同步代碼時,如果此時鎖已經(jīng)被其他線程取走,則該線程會進(jìn)入到“等鎖池”,直到持有鎖的那個線程釋放掉鎖并且自身獲取到鎖之后,自身會轉(zhuǎn)為可運行狀態(tài)。

例子如下:

StringBufferThread:

  1. import java.util.concurrent.CountDownLatch;
  2. public class StringBufferThread implements Runnable {
  3. StringBuffer sb;
  4. CountDownLatch countDownLatch;
  5. StringBufferThread(StringBuffer sb, CountDownLatch countDownLatch) {
  6. this.sb = sb;
  7. this.countDownLatch = countDownLatch;
  8. }
  9. @Override
  10. public void run() {
  11. // StringBufferThread這個類作為鎖
  12. synchronized (StringBufferThread.class) {
  13. try {
  14. // 睡眠10秒,因為主線程在調(diào)用本線程5秒后就會調(diào)用第二個子線程,多睡眠5秒,就能看出效果
  15. Thread.sleep(10000);
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. sb.append("This is StringBufferThread1\n");
  20. countDownLatch.countDown();
  21. }
  22. }
  23. }

StringBufferThread2:

  1. import java.util.concurrent.CountDownLatch;
  2. public class StringBufferThread2 implements Runnable{
  3. StringBuffer sb;
  4. CountDownLatch countDownLatch;
  5. StringBufferThread2(StringBuffer sb, CountDownLatch countDownLatch) {
  6. this.sb = sb;
  7. this.countDownLatch = countDownLatch;
  8. }
  9. @Override
  10. public void run() {
  11. // StringBufferThread這個類作為鎖
  12. synchronized (StringBufferThread.class) {
  13. sb.append("This is StringBufferThread2\n");
  14. countDownLatch.countDown();
  15. }
  16. }
  17. }

main方法不變:

  1. public static void main(String[] args) throws InterruptedException {
  2. StringBuffer tipStr = new StringBuffer();
  3. // 使用CountDownLatch保證子線程全部執(zhí)行完成后主線程才打印結(jié)果
  4. CountDownLatch countDownLatch = new CountDownLatch(2);
  5. StringBufferThread stringBufferThread = new StringBufferThread(tipStr, countDownLatch);
  6. StringBufferThread2 stringBufferThread2 = new StringBufferThread2(tipStr, countDownLatch);
  7. Thread thread1 = new Thread(stringBufferThread);
  8. Thread thread2 = new Thread(stringBufferThread2);
  9. thread1.start();
  10. /*
  11. 為了保證先讓thread1執(zhí)行,我們讓thread1執(zhí)行后主線程睡眠5秒鐘再執(zhí)行thread2,
  12. 如果不進(jìn)行睡眠的話我們無法控制CPU分配時間片段,有可能直接就先分配給thread2線程了,
  13. 這樣就會造成thread2先于thread1執(zhí)行
  14. */
  15. Thread.sleep(5000);
  16. thread2.start();
  17. // 調(diào)用countDownLatch.await()保證子線程全部執(zhí)行完后主線程才繼續(xù)執(zhí)行
  18. countDownLatch.await();
  19. System.out.println(tipStr.toString());
  20. }

執(zhí)行結(jié)果如下:

由此可見,主線程調(diào)用thread1后的5秒后調(diào)用了thread2,thread1在執(zhí)行時首先拿走了鎖對象并睡眠了10秒,在這10秒鐘,thread2有5秒的時間(10秒減去主線程等待的5秒)去執(zhí)行run方法中的字符串追加操作,但是因為鎖已經(jīng)被thread1拿走了,所以thread2在這漫長的5秒鐘之內(nèi)什么都做不了,只能等待thread1將字符串"This is StringBufferThread1\n"先追加到StringBuffer的開頭,然后才能把自己的字符串"This is StringBufferThread2\n"追加到StringBuffer的末尾。

4.3 其他阻塞:

1)線程中執(zhí)行了 Thread.sleep(xx) 方法進(jìn)行休眠會進(jìn)入阻塞狀態(tài),直到Thread.sleep(xx)方法休眠的時間超過參數(shù)設(shè)定的時間而超時后線程會轉(zhuǎn)為可運行狀態(tài)。Thread.sleep(xx)方法的使用在本文很多例子都體現(xiàn)了,就不演示了。

2)線程ThreadA中調(diào)用了ThreadB.join()方法來等待ThreadB線程執(zhí)行完畢,從而ThreadA進(jìn)入阻塞狀態(tài),直到ThreadB線程執(zhí)行完畢后ThreadA會轉(zhuǎn)為可運行狀態(tài)。

例子如下:

StringBufferThread:

  1. import java.util.concurrent.CountDownLatch;
  2. public class StringBufferThread implements Runnable {
  3. StringBuffer sb;
  4. CountDownLatch countDownLatch;
  5. Thread thread2;
  6. StringBufferThread(StringBuffer sb, CountDownLatch countDownLatch, Thread thread2) {
  7. this.sb = sb;
  8. this.countDownLatch = countDownLatch;
  9. this.thread2 = thread2;
  10. }
  11. @Override
  12. public void run() {
  13. try {
  14. // 這里阻塞住,等待thread2執(zhí)行完畢才會繼續(xù)向下執(zhí)行
  15. thread2.join();
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. sb.append("This is StringBufferThread1\n");
  20. countDownLatch.countDown();
  21. }
  22. }

StringBufferThread2:

  1. import java.util.concurrent.CountDownLatch;
  2. public class StringBufferThread2 implements Runnable {
  3. StringBuffer sb;
  4. CountDownLatch countDownLatch;
  5. StringBufferThread2(StringBuffer sb, CountDownLatch countDownLatch) {
  6. this.sb = sb;
  7. this.countDownLatch = countDownLatch;
  8. }
  9. @Override
  10. public void run() {
  11. /*
  12. thread2睡眠3秒,就能看出效果,如果join()失效,
  13. 那么StringBuffer中一定是"This is StringBufferThread1\n"開頭的
  14. */
  15. try {
  16. Thread.sleep(3000);
  17. } catch (InterruptedException e) {
  18. e.printStackTrace();
  19. }
  20. sb.append("This is StringBufferThread2\n");
  21. countDownLatch.countDown();
  22. }
  23. }

隨后,修改main方法:

  1. public static void main(String[] args) throws InterruptedException {
  2. StringBuffer tipStr = new StringBuffer();
  3. // 使用CountDownLatch保證子線程全部執(zhí)行完成后主線程才打印結(jié)果
  4. CountDownLatch countDownLatch = new CountDownLatch(2);
  5. StringBufferThread2 stringBufferThread2 = new StringBufferThread2(tipStr, countDownLatch);
  6. Thread thread2 = new Thread(stringBufferThread2);
  7. StringBufferThread stringBufferThread = new StringBufferThread(tipStr, countDownLatch, thread2);
  8. Thread thread1 = new Thread(stringBufferThread);
  9. thread1.start();
  10. thread2.start();
  11. // 調(diào)用countDownLatch.await()保證子線程全部執(zhí)行完后主線程才繼續(xù)執(zhí)行
  12. countDownLatch.await();
  13. System.out.println(tipStr.toString());
  14. }

執(zhí)行結(jié)果如下:

由此可見,雖然thread1先于thread2執(zhí)行,但是因為在將字符串追加到StringBuffer對象前調(diào)用了thread2.join(),便被阻塞住了,此時thread2睡眠三秒后,將字符串"This is StringBufferThread2\n"追加到了StringBuffer對象的開頭,thread2執(zhí)行完畢;隨后因為thread1等待的thread2已經(jīng)執(zhí)行完畢了,thread1便由阻塞狀態(tài)轉(zhuǎn)為可運行狀態(tài),在分配到CPU的時間片段后,便將字符串"This is StringBufferThread1\n"追加到了StringBuffer對象的結(jié)尾。

3)線程中進(jìn)行了I/O操作,I/O操作在輸入輸出行為執(zhí)行完畢之前都不會返回給調(diào)用者任何結(jié)果,直到I/O操作執(zhí)行完畢之后線程會轉(zhuǎn)為可運行狀態(tài)。

例如:

我們編寫ThreadTest類:

  1. import java.util.Scanner;
  2. public class ThreadTest implements Runnable {
  3. @Override
  4. public void run() {
  5. System.out.println("This is StringBufferThread1 Begin\n");
  6. Scanner scanner = new Scanner(System.in);
  7. System.out.println("請輸入內(nèi)容:");
  8. // 線程會阻塞在這,等待用戶在控制臺輸入數(shù)據(jù)后繼續(xù)執(zhí)行
  9. String content = scanner.nextLine();
  10. System.out.println("您輸入的內(nèi)容是:" + content + "\n");
  11. System.out.println("This is StringBufferThread1 end\n");
  12. }
  13. }

執(zhí)行main方法:

  1. public static void main(String[] args) throws InterruptedException {
  2. ThreadTest threadTest = new ThreadTest();
  3. Thread thread1 = new Thread(threadTest);
  4. thread1.start();
  5. }

執(zhí)行效果如下:

線程會阻塞在這里等待我們從控制臺輸入內(nèi)容。

輸入內(nèi)容后,線程繼續(xù)運行。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多