天天看點

線程常用方法-并發程式設計(Java)

文章目錄

    • 1、yield
    • 2、線程優先級
    • 3、join
    • 4、sleep
    • 5、interrupt
    • 6、兩階段終止模式
    • 7、park
    • 8、守護線程

線程常用方法

1、yield

  1. 調用yield使目前線程沖Running狀态轉為Runnable狀态,讓具有相同優先級的其他線程獲得運作機會。但是,實際中無法保證yield()達到讓步目的,因為讓步的線程還有可能被線程排程程式再次選中。
  2. 具體實作依賴于作業系統的排程器

2、線程優先級

  • Java 線程優先級使用 1 ~ 10 的整數表示:
    • 最低優先級 1:

      Thread.MIN_PRIORITY

    • 最高優先級 10:

      Thread.MAX_PRIORITY

    • 普通優先級 5(預設優先級):

      Thread.NORM_PRIORITY

  • 擷取線程優先級 :線程對象.getPriority()
  • 設定線程優先級: 先處對象.setPriority()
  • 高優先級的線程比低優先級的線程有更高的幾率得到執行,實際上這和作業系統及虛拟機版本相關,有可能即使設定了線程的優先級也不會産生任何作用。

參考文章:Java 多線程:線程優先級

3、join

  • 線程對象.join():無限時同步,目前線程一直等待調用join()方法的線程執行完畢,才繼續執行
  • 線程對象.join(long millis):限時同步,最多等待millis毫秒後,目前線程繼續執行
private static int r = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                Thread.sleep(1000);
                r = 10;
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        t1.start();

        log.debug("r = " + r);
           

結果:r=0,如何讓程式列印10呢?想要列印10,主線程必須等待t1線程執行完畢,此時可在列印之前執行t1.join()方法

// 其他代碼與上面相同...
 t1.start();
 t1.join();
 log.debug("r = " + r);
           

當給join設定參數時,結果又會如何呢?

t1.start();
t1.join(100);
log.debug("r = " + r);
           

結果:r=0

4、sleep

sleep

  1. 調用sleep方法,目前線程從Running狀态轉為Timed Waiting狀态
  2. 其他線程可以執行interrupt方法打斷在在睡眠的線程,這時會抛出InterruptedException
  3. 調用sleep方法,目前線程不會釋放對象鎖标記
  4. 建議使用TimeUnit的sleep代替Thread的sleep來獲得更好的可讀性
public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (true) {
                try {
                    Thread.sleep(1000);
                    log.debug("t1 線程執行...");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t1.start();
        while (true) {
            TimeUnit.SECONDS.sleep(1);
            log.debug("主線程執行...");
        }
    }
           

用Thread的sleep方法設定睡眠1s和用TimeUnit的sleep設定睡眠1s,可以很直覺的看出後一種的可讀性更高。

TimeUnit還可以設定納秒、微妙、秒、分鐘、小時、天等等機關。

5、interrupt

  1. 可以打斷正常運作的線程,此時打斷标記為true,此時不會清除打斷标記(值為true)
  2. 可以打斷調用sleep()、wait()、join()等處于阻塞狀态的線程,此時會抛出InterruptedException異常;同時會清除打斷标記,既打斷标志設定為false
  3. 擷取打斷标記方法isInterrupted(),值為true/false。
@Slf4j(topic = "cm.test01")
public class CMInterrupt {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
                log.debug("sleep...");
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
        });

        t1.start();
        Thread.sleep(100);
        log.debug("interrupt...");
        t1.interrupt();
        log.debug("t1 的打斷标記:{}", t1.isInterrupted());
    }
}

2021-08-05 20:55:20 DEBUG [Thread-1] cm.test01 - sleep...
2021-08-05 20:55:20 DEBUG [main] cm.test01 - interrupt...
2021-08-05 20:55:20 DEBUG [main] cm.test01 - t1 的打斷标記:false
java.lang.InterruptedException: sleep interrupted
           

在來看下打斷正常運作的線程:

@Slf4j(topic = "cm.CMInterruptNormal")
public class CMInterruptNormal {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
                while (true) {

                }
        }, "t1");

        t1.start();
        Thread.sleep(100);
        log.debug("interrupt...");
        t1.interrupt();
        log.debug("t1 的打斷标記:{}", t1.isInterrupted());
    }
}

2021-08-05 21:01:41 DEBUG [main] cm.CMInterruptNormal - interrupt...
2021-08-05 21:01:41 DEBUG [main] cm.CMInterruptNormal - t1 的打斷标記:true

           

線程t1一直在運作,那麼當線程被打斷了,如果讓線程t1停下來呢?此時,可以通過判斷标記來結束線程執行。

// 其他代碼同上
Thread t1 = new Thread(() -> {
                while (true) {
                    if (Thread.currentThread().isInterrupted()) break;
                }
        }, "t1");
           

6、兩階段終止模式

一般應用于需要随系統一直執行的某些子產品,比如監控子產品等。此時,監控子產品設定為while(true),監控子產品在不需要實施執行時,用sleep()模拟;那麼監控子產品可以處于2種狀态:1、執行正常的監控記錄等程式 2、處于睡眠狀态。那麼當線程被打斷時,如何使在可控的情況下結束監控子產品,而不影響系統其他子產品的運作呢?

圖示:[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-Lr95RLtg-1628172174411)(I:\study\java\note\concurrent\phrase2-termination-mode.png)]

代碼:

@Slf4j(topic = "cm.CMPhase2terminationMode")
public class CMPhase2terminationMode {
    public static void main(String[] args) throws InterruptedException {
        Thread monitor = new Thread(() -> {
                while (true) {
                    // 判斷是否打斷
                    if (Thread.currentThread().isInterrupted()) {
                        log.debug("處理善後");
                        break;
                    }

                    try {
                        // 睡眠
                        TimeUnit.SECONDS.sleep(1);
                        // 監控記錄
                        log.debug("監控記錄執行...");
                    } catch (InterruptedException e) {
                        // 設定打斷标記
                        Thread.currentThread().interrupt();
                        e.printStackTrace();
                    }
                }
        }, "t1");

        log.debug("監控子產品開始執行...");
        monitor.start();
        TimeUnit.MILLISECONDS.sleep(3500);
        // 模拟意外打斷情況
        monitor.interrupt();
        log.debug("監控子產品關閉...");
    }
}
           

如果不在catch子產品中添加重新設定打斷标記,那麼程式就不會結束。

7、park

  1. LockSupport的方法,會讓線程無限時暫停執行
  2. interrupt()方法可打斷暫停狀态,讓線程繼續執行
  3. 打斷标記為true時,park方法不會生效
  4. interrupted()判斷打斷标記,同時會清除打斷标記

代碼測試:

@Slf4j(topic = "cm.CMPark")
public class CMPark {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("park...");
            LockSupport.park();
            log.debug("unpark...");
            log.debug("線程狀态:{}", Thread.currentThread().isInterrupted());
            LockSupport.park();
            log.debug("unpark...");
        });

        t1.start();
        TimeUnit.SECONDS.sleep(1);
        t1.interrupt();
    }
}

2021-08-05 21:50:13.084 DEBUG [Thread-1] cm.CMPark - park...
2021-08-05 21:50:14.083 DEBUG [Thread-1] cm.CMPark - unpark...
2021-08-05 21:50:14.083 DEBUG [Thread-1] cm.CMPark - 線程狀态:true
2021-08-05 21:50:14.084 DEBUG [Thread-1] cm.CMPark - unpark...
           

1秒之後執行打斷,當再次執行park的時候,沒有生效,此時,可以通過interrupted()方法判斷并清除标記

log.debug("unpark...");
log.debug("線程狀态:{}", Thread.interrupted());
LockSupport.park();
           

8、守護線程

Java程序,隻有當其所有線程執行結束的時候,才會結束。守護線程,顧名思義,不管守護線程有沒有執行完成,目前守護的線程結束的時候,守護線程也會結束執行。

代碼示例:

@Slf4j(topic = "cm.CMDaemon")
public class CMDaemon {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (true) {
                if (Thread.currentThread().isInterrupted()) break;
            }
            log.debug("守護線程執行結束...");
        }, "t1");
        t1.setDaemon(true);
        t1.start();
        TimeUnit.SECONDS.sleep(1);
        log.debug("主線程執行結束...");
    }
}
           

tips:

  1. 如果一個線程要設定為守護線程,通過調用setDaemon(true)方法,一定要線上程啟動之前設定,否則無效

繼續閱讀