多線程——線程的常用方法

sleep

sleep是一個靜態(tài)方法,只有兩個重載方法,其中一個傳入毫秒數(shù), 另一個既需要毫秒數(shù)也需要納秒數(shù)。

public static native void sleep(long millis) throws InterruptedException;
public static void sleep(long millis, int nanos) throws InterruptedException

sleep方法會使當(dāng)前線程進(jìn)入指定毫秒的休眠,暫停執(zhí)行,雖然給定了休眠的時間,但是最終要以系統(tǒng)的定時器和調(diào)度器的精度為準(zhǔn),
sleep的休眠有一個很重要的特性,就是不會放棄monitor鎖的所有權(quán)

JDK 1.5以后,引入了一個枚舉TimeUnit 其對sleep方法進(jìn)行了很好的封裝

    public void sleep(long timeout) throws InterruptedException {
        if (timeout > 0) {
            long ms = toMillis(timeout);
            int ns = excessNanos(timeout, ms);
            Thread.sleep(ms, ns);
        }
    }

使用實(shí)例

      TimeUnit.SECONDS.sleep(10);
      TimeUnit.MILLISECONDS.sleep(200);
      TimeUnit.HOURS.sleep(1);

yield

yield方法屬于一種啟發(fā)式的方法,其會提醒調(diào)度器我愿意放棄當(dāng)前的CPU資源,如果CPU資源不緊張,則會忽略這個提醒。即yield只是一個提示,CPU調(diào)度器并不會擔(dān)保每次都能滿足yield的提示。

yield VS sleep

  1. 是否導(dǎo)致線程上下文的切換
    sleep會導(dǎo)致當(dāng)前線程暫停指定的時間,沒有CPU的時間片消耗

yield只會CPU調(diào)度器發(fā)一個提示,如果CPU調(diào)度器不緊張則會忽略這個提示,如果沒有忽略這個提示,會導(dǎo)致線程上下文的切換

  1. 導(dǎo)致的線程狀態(tài)切換
    sleep 會導(dǎo)致線程短暫進(jìn)入Block, 會在給定的時間內(nèi)釋放CPU資源
    yield 會是Running狀態(tài)的線程進(jìn)入Runnable的狀態(tài)(如果CPU調(diào)度器沒有忽略這個提示的話)

  2. 時間上
    sleep 幾乎百分百完成給定時間的休眠
    yield 的提示并不能一定擔(dān)保

  3. 中斷信號
    一個線程sleep 另一個線程調(diào)用interrupt 會捕獲到中斷信號
    yield則不會

priority

線程的優(yōu)先級,理論上優(yōu)先級比較高的線程會獲得優(yōu)先被CPU調(diào)度的機(jī)會。但是事實(shí)上并不會如你所愿,設(shè)置線程的優(yōu)先級也是一個hint知操作:

  • 對于root用戶,它會hint操作系統(tǒng)你想要的設(shè)置的優(yōu)先級別,否則它會被忽略
  • 如果CPU太忙,設(shè)置優(yōu)先級可能會獲得更多CPU時間片,但是閑時優(yōu)先級的高低幾乎不會有任何作用。
    所以,不要在程序設(shè)計(jì)中企圖用線程的優(yōu)先級來綁定某些特性業(yè)務(wù),或者讓業(yè)務(wù)嚴(yán)重依賴線程的優(yōu)先級。

線程的默認(rèn)優(yōu)先級

    /**
     * The minimum priority that a thread can have.
     */
    public static final int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public static final int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public static final int MAX_PRIORITY = 10;

    public final void setPriority(int newPriority) {
        ThreadGroup g;
        checkAccess();
        if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
            throw new IllegalArgumentException();
        }
        if((g = getThreadGroup()) != null) {
            if (newPriority > g.getMaxPriority()) {
                newPriority = g.getMaxPriority();
            }
            setPriority0(priority = newPriority);
        }
    }

上面源碼分析得出,線程的優(yōu)先級不能大于10 和小于1 , 如果指定線程的優(yōu)先級大于線程所在組的最大優(yōu)先級,則指定優(yōu)先級將會失效,取而代之的是當(dāng)前線程所在組的最大線程優(yōu)先級。如果小于所在組的最大線程優(yōu)先級, 則設(shè)置為需要設(shè)置的線程優(yōu)先級。

線程的上下文類加載器

線程的上下文類加載器,就是這個線程是有那個類加載器加載的,如果在沒有修改線程上下文類加載器的情況下,則保持與父線程同樣的類加載器
如果設(shè)置該線程的類加載器,這個方法則可以打破Java類加載器的父委托機(jī)制,有時也稱為Java類加載器的后門。

interrupt

public void interrupt();
/**
此方法是一個靜態(tài)方法,雖然也用于對當(dāng)前線程是否被中斷,但是和成員方法isInterrupted方法的區(qū)別,
就是調(diào)用該方法會直接擦除掉線程的interrupt標(biāo)識,

注意:如果當(dāng)前線程被打斷了,那么第一次調(diào)用interrupted方法會返回true, 
并且立即擦除interrupt標(biāo)識,  第二次包括以后調(diào)用永遠(yuǎn)都會返回false, 
除非再次期間線程又一次被打斷
*/
public static boolean interrupted();
/**
Thread的成員方法,主要是判斷當(dāng)前線程是否被中斷,該方法
僅僅是對interrupt標(biāo)識的一個判斷,并不會影響標(biāo)識發(fā)生任何改變
*/
public boolean isInterrupted();

調(diào)用如下方法會使當(dāng)前線程進(jìn)入阻塞狀態(tài)

  1. Object 的wait()
  2. Object 的wait(long)
  3. Object 的wait(long, int)
  4. Thread 的sleep(long)
  5. Thread 的sleep(long, int)
  6. Thread 的join()
  7. Thread 的join(long)
  8. Thread 的join(long, int)
  9. InterruptibleChannel的IO操作
  10. Selector的wakeup方法
  11. 其他

而調(diào)用當(dāng)前線程的Interrupt方法,則就可以打斷阻塞, 因此此方法也稱為可中斷方法。
注意:
打斷一個線程不等于該線程的生命周期結(jié)束,僅僅是打斷了當(dāng)前線程的阻塞狀態(tài)。
如果一個線程執(zhí)行可中斷方法被阻塞時,調(diào)用interrupt方法將其中斷,會導(dǎo)致其中斷標(biāo)識被清除。
如果一個線程已經(jīng)試死亡狀態(tài),那么嘗試對其調(diào)用interrupt方法會直接被忽略

join

與sleep一樣, 它也是一個可中斷方法。也即如果有其他線程執(zhí)行了對當(dāng)前線程的interrupt操作,它也會捕捉到中斷信號,并且擦除線程的interrupt標(biāo)識。
join某個線程A , 會使當(dāng)前線程B(比如main線程)進(jìn)入等待,知道線程A結(jié)束生命周期,或者到達(dá)指定的時間,那么在此期間線程B是Blocked的

實(shí)例:
比如需要你做一個接口請求,這個請求的返回需要調(diào)用多個接口的返回進(jìn)行組合,這里用join來做并行多個請求,等都返回后再進(jìn)行組合,當(dāng)然也可用JDK自帶的CountDownLatch 或者CyclicBarrier等,這里我們用join來實(shí)現(xiàn)一把。

public class ClientRequestTask extends Thread {

    public static void main(String[] args) {
        List<ClientRequestTask> tasks = IntStream.range(1, 3).boxed()
                .map(url -> new ClientRequestTask("url:" + url)).collect(Collectors.toList());

        tasks.forEach(Thread::start);

        tasks.forEach(task -> {
            try {
                task.join();
            } catch (InterruptedException e) {

            }
        });

        List<String> list = new ArrayList<>();
        tasks.stream().map(ClientRequestTask::getResults).forEach(list::addAll);
        System.out.println(list);
    }

    private String url;

    private final List<String> results = new ArrayList<>();

    public ClientRequestTask(String url) {
        this.url = url;
    }


    public List<String> getResults() {
        return results;
    }

    @Override
    public void run() {
        try {
            System.out.println(url + " start request data");
            int i = ThreadLocalRandom.current().nextInt(10);
            TimeUnit.SECONDS.sleep(ThreadLocalRandom.current().nextInt(10));
            results.add(url + "---------" + i);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

如何關(guān)閉一個線程

  1. JDK有一個Deprecated的方法stop, 但是該方法關(guān)閉線程時不會釋放掉monitor鎖,所以官方已經(jīng)不推薦使用此方法了。
  2. 線程結(jié)束生命周期
  3. 捕獲中斷信號關(guān)閉線程
  4. 使用volatile開關(guān)控制
  5. 異常退出
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容