Java多線程中join、yield、sleep方法詳解

在Java多線程編程中,Thread類是其中一個核心和關鍵的角色。所以,對該類中一些基礎經常使用方法的理解和熟練使用是開發多線程代碼的基礎。本篇主要總結一下Thread中經常使用的一些靜態方法的含義及代碼中的使用。java

sleep方法

源碼以下:編程

/**
     * Causes the currently executing thread to sleep (temporarily cease
     * execution) for the specified number of milliseconds, subject to
     * the precision and accuracy of system timers and schedulers. The thread
     * does not lose ownership of any monitors.
     *
     * @param  millis
     *         the length of time to sleep in milliseconds
     *
     * @throws  IllegalArgumentException
     *          if the value of {@code millis} is negative
     *
     * @throws  InterruptedException
     *          if any thread has interrupted the current thread. The
     *          <i>interrupted status</i> of the current thread is
     *          cleared when this exception is thrown.
     */
    public static native void sleep(long millis) throws InterruptedException;

能夠看到sleep是一個靜態的本地方法,由於是本地方法,因此並無java代碼的實現,實際上是調用了底層的C庫函數來實現的睡眠。多線程

有一個long類型的參數,表示睡眠多少毫秒。併發

閱讀註釋,sleep方法的含義就是,讓當前正在執行任務的線程睡眠(臨時地中止執行)指定的毫秒數,這個精度和準確性是用系統時鐘和調度器保證的。可是,線程並不會釋放它擁有的鎖。app

注意該方法會拋出InterruptedException中斷異常。ide

sleep不會釋放鎖代碼示例:函數

public class ThreadsleepDemo{

    private Object object = new Object();

    public static void main(String[] args) {

        ThreadsleepDemo threadsleepDemo = new ThreadsleepDemo();
        Thread thread1 = threadsleepDemo.new SleepDemoThread();
        thread1.setName("線程1");
        Thread thread2 = threadsleepDemo.new SleepDemoThread();
        thread2.setName("線程2");
        thread1.start();
        thread2.start();

    }

    class SleepDemoThread extends Thread{
        @Override
        public void run() {
            synchronized (object){
                System.out.println(Thread.currentThread().getName());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName());
            }
        }
    }
}

輸出結果以下:oop

線程1開始運行
線程1運行結束
線程2開始運行
線程2運行結束

能夠多運行幾回,可能會有線程1在上面或和線程2在上面,但始終都是一個行程運行完了纔會運行另外一個線程,中間不會插入進來一個線程運行。性能

yield方法

/**
     * A hint to the scheduler that the current thread is willing to yield
     * its current use of a processor. The scheduler is free to ignore this
     * hint.
     *
     * <p> Yield is a heuristic attempt to improve relative progression
     * between threads that would otherwise over-utilise a CPU. Its use
     * should be combined with detailed profiling and benchmarking to
     * ensure that it actually has the desired effect.
     *
     * <p> It is rarely appropriate to use this method. It may be useful
     * for debugging or testing purposes, where it may help to reproduce
     * bugs due to race conditions. It may also be useful when designing
     * concurrency control constructs such as the ones in the
     * {@link java.util.concurrent.locks} package.
     */
    public static native void yield();

當前線程對調度器的一個暗示,表示願意讓出CPU執行器的當前使用權,可是調度器能夠自由忽略這個提示。測試

Yeild是一種在可能會過分使用一個CPU的多個線程之間提高相對進度試探性嘗試。它的使用應該結合詳細的性能分析和基準測試來進行,確保它確實有預期的效果。

不多使用這種方法。 它可能對調試或測試有用,可能有助於根據競態條件重現錯誤。 在設計併發控制結構(例如java.util.concurrent.locks包中的並行控制結構)時也可能有用。

join方法

join有三個重載的方法

join()
join(long millis)    
join(long millis,int nanoseconds)

主要看下第二個方法的源碼

/**
     * Waits at most {@code millis} milliseconds for this thread to
     * die. A timeout of {@code 0} means to wait forever.
     *
     * <p> This implementation uses a loop of {@code this.wait} calls
     * conditioned on {@code this.isAlive}. As a thread terminates the
     * {@code this.notifyAll} method is invoked. It is recommended that
     * applications not use {@code wait}, {@code notify}, or
     * {@code notifyAll} on {@code Thread} instances.
     */
    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

就是等待一個線程指定毫秒數後再消亡。無參數的join方法其實就是調用了join(0),即永遠等待下去。不過經過源碼咱們能夠看到,在while循環中有一個條件判斷,即isAlive()方法,意思是若是當前線程還活着,就會一直等待下去。

有點懵,看個例子應該加深下理解。好比睡前想刷個抖音

刷抖音的工做咱們交給一個線程來完成。

public class ScanDouyin extends Thread{

    // 瀏覽抖音的時長
    private int scanTime;

    public ScanDouyin(String name, int scanTime){
        super(name);
        scanTime = this.scanTime;
    }
    @Override
    public void run() {
        System.out.println(getName() + ":開始刷抖音了");
        try {
            // 刷抖音的時間
            sleep(scanTime);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(getName() +":抖音刷完了,睡覺吧");
    }
}

下面是準備睡覺的線程

/**
 *  準備睡覺了,睡前想要刷個抖音
 */
public class ReadySleep extends Thread{

    private ScanDouyin scanDouyin;

    public ReadySleep(String name,ScanDouyin scanDouyin){
        super(name);
        this.scanDouyin = scanDouyin;
    }
    @Override
    public void run() {
        System.out.println(getName() + ":準備開始睡覺啦");
        try {
            // 睡前刷把抖音
            scanDouyin.join();
           
            // 準備睡覺的具體內容
            System.out.println("開始睡覺");
            sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(getName() + ":zzzzzzzz,已經睡着了");
    }

    public static void main(String[] args) {
        ScanDouyin scanDouyin = new ScanDouyin("刷抖音線程",10000);
        ReadySleep readySleep = new ReadySleep("睡覺線程",scanDouyin);
        readySleep.start();
        scanDouyin.start();

    }
}

輸出結果以下:

睡覺線程:準備開始睡覺啦
刷抖音線程:開始刷抖音了
刷抖音線程:抖音刷完了,睡覺吧
開始睡覺
睡覺線程:zzzzzzzz,已經睡着了

這裏咱們我設置的刷抖音的時間是10s,睡覺線程的執行時間是100ms,也就是0.1s。

能夠看到由於在睡覺線程中調用了刷抖音線程的join方法,使得睡覺的線程必須等待直到刷完抖音(刷抖音線程執行完畢,線程消亡),才能開始睡覺。

至此,應該能夠明白,若是某個線程在另外一個線程t上調用t.join(),此線程將被掛起,直到目標線程t結束才恢復(即t.isAlive()方法返回假)。

相關文章
相關標籤/搜索