二、Thread常用方法

目錄

1.Thread相關源碼

2.start()與run()

3. sleep() 與 interrupt()

4.wait() 與 notify()

5.sleep() 與 wait()

6.join()


1.Thread相關源碼

public class Thread implements Runnable {
    // 線程名字
    private volatile String name;
    // 線程優先級(1~10)
    private int priority;
    // 守護線程
    private boolean daemon = false;
    // 線程id
    private long tid;
    // 線程組
    private ThreadGroup group;
    
    // 預定義3個優先級
    public final static int MIN_PRIORITY = 1;
    public final static int NORM_PRIORITY = 5;
    public final static int MAX_PRIORITY = 10;
    
    
    // 構造函數
    public Thread();
    public Thread(String name);
    public Thread(Runnable target);
    public Thread(Runnable target, String name);
    // 線程組
    public Thread(ThreadGroup group, Runnable target);
    
    
    // 返回當前正在執行線程對象的引用
    public static native Thread currentThread();
    
    // 啓動一個新線程
    public synchronized void start();
    // 線程的方法體,和啓動線程沒毛關係
    public void run();
    
    // 讓線程睡眠一會,由活躍狀態改爲掛起狀態
    public static native void sleep(long millis) throws InterruptedException;
    public static void sleep(long millis, int nanos) throws InterruptedException;
    
    // 打斷線程 中斷線程 用於停止線程
    // 調用該方法時並不需要獲取Thread實例的鎖。無論何時,任何線程都可以調用其它線程的interruptf方法
    public void interrupt();
    public boolean isInterrupted()
    
    // 線程是否處於活動狀態
    public final native boolean isAlive();
    
    // 交出CPU的使用權,從運行狀態改爲掛起狀態
    public static native void yield();
    
    public final void join() throws InterruptedException
    public final synchronized void join(long millis)
    public final synchronized void join(long millis, int nanos) throws InterruptedException
    
    
    // 設置線程優先級
    public final void setPriority(int newPriority);
    // 設置是否守護線程
    public final void setDaemon(boolean on);
    // 線程id
    public long getId() { return this.tid; }
    
    
    // 線程狀態
    public enum State {
        // new 創建
        NEW,

        // runnable 就緒
        RUNNABLE,

        // blocked 阻塞
        BLOCKED,

        // waiting 等待
        WAITING,

        // timed_waiting
        TIMED_WAITING,

        // terminated 結束
        TERMINATED;
    }
}
public static void main(String[] args) {
    // main方法就是一個主線程

    // 獲取當前正在運行的線程
    Thread thread = Thread.currentThread();
    // 線程名字
    String name = thread.getName();
    // 線程id
    long id = thread.getId();
    // 線程優先級
    int priority = thread.getPriority();
    // 是否存活
    boolean alive = thread.isAlive();
    // 是否守護線程
    boolean daemon = thread.isDaemon();

    // Thread[name=main, id=1 ,priority=5 ,alive=true ,daemon=false]
    System.out.println("Thread[name=" + name + ", id=" + id + " ,priority=" + priority + " ,alive=" + alive + " ,daemon=" + daemon + "]");
}

2.start()與run()

  • start(): 啓動一個線程,線程之間是沒有順序的,是按CPU分配的時間片來回切換的。
  • run(): 調用線程的run方法,就是普通的方法調用,雖然將代碼封裝到兩個線程體中,可以看到線程中打印的線程名字都是main主線程,run()方法用於封裝線程的代碼,具體要啓動一個線程來運行線程體中的代碼(run()方法)還是通過start()方法來實現,調用run()方法就是一種順序編程不是併發編程。

3. sleep() 與 interrupt()

public static native void sleep(long millis) throws InterruptedException;
public void interrupt();

sleep(long millis): 睡眠指定時間,程序暫停運行,睡眠期間會讓出CPU的執行權,去執行其它線程,同時CPU也會監視睡眠的時間,一旦睡眠時間到就會立刻執行(因爲睡眠過程中仍然保留着鎖,有鎖只要睡眠時間到就能立刻執行)。

  • sleep(): 睡眠指定時間,即讓程序暫停指定時間運行,時間到了會繼續執行代碼,如果時間未到就要醒需要使用interrupt()來隨時喚醒;
  • interrupt(): 喚醒正在睡眠的程序,調用interrupt()方法,會使得sleep()方法拋出InterruptedException異常,當sleep()方法拋出異常就中斷了sleep的方法,從而讓程序繼續運行下去。
public static void main(String[] args) throws Exception {
    Thread thread0 = new Thread(()-> {
        try {
            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t太困了,讓我睡10秒,中間有事叫我,zZZ。。。");
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t被叫醒了,又要繼續幹活了");
        }
    });
    thread0.start();

    // 這裏睡眠只是爲了保證先讓上面的那個線程先執行
    Thread.sleep(2000);

    new Thread(()-> {
        System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t醒醒,醒醒,別睡了,起來幹活了!!!");
        // 無需獲取鎖就可以調用interrupt
        thread0.interrupt();
    }).start();
}

4.wait() 與 notify()

wait、notify和notifyAll方法是Object類的final native方法。所以這些方法不能被子類重寫,Object類是所有類的超類,因此在程序中可以通過this或者super來調用this.wait(), super.wait()。

  • wait(): 導致線程進入等待阻塞狀態,會一直等待直到它被其他線程通過notify()或者notifyAll喚醒。該方法只能在同步方法中調用。如果當前線程不是鎖的持有者,該方法拋出一個IllegalMonitorStateException異常。wait(long timeout): 時間到了自動執行,類似於sleep(long millis)。
  • notify(): 該方法只能在同步方法或同步塊內部調用, 隨機選擇一個(注意:只會通知一個)在該對象上調用wait方法的線程,解除其阻塞狀態。
  • notifyAll(): 喚醒所有的wait對象。

注意:

  • Object.wait()和Object.notify()和Object.notifyall()必須寫在synchronized方法內部或者synchronized塊內部。
  • 讓哪個對象等待wait就去通知notify哪個對象,不要讓A對象等待,結果卻去通知B對象,要操作同一個對象

Object類

public class Object {
	public final void wait() throws InterruptedException;
	public final native void wait(long timeout) throws InterruptedException;
	public final void wait(long timeout, int nanos) throws InterruptedException;
	
	
	public final native void notify();
	public final native void notifyAll();
}

測試:

public class WaitNotifyTest {
    public static void main(String[] args) throws Exception {
        WaitNotifyTest waitNotifyTest = new WaitNotifyTest();
        new Thread(() -> {
            try {
                waitNotifyTest.printFile();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }).start();

        new Thread(() -> {
            try {
                waitNotifyTest.printFile();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }).start();

        new Thread(() -> {
            try {
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t睡覺1秒中,目的是讓上面的線程先執行,即先執行wait()");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            waitNotifyTest.notifyPrint();
        }).start();
    }

    private synchronized void printFile() throws InterruptedException {
        System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t等待打印文件...");
        this.wait();
        System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t打印結束。。。");
    }

    private synchronized void notifyPrint() {
        this.notify();
        System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t通知完成...");
    }
}

è¿éåå¾çæè¿°

wait():讓程序暫停執行,相當於讓當前,線程進入當前實例的等待隊列,這個隊列屬於該實例對象,所以調用notify也必須使用該對象來調用,不能使用別的對象來調用。調用wait和notify必須使用同一個對象來調用。

this.notifyAll();

è¿éåå¾çæè¿°

5.sleep() 與 wait()

區別:

  • sleep在Thread類中,wait在Object類中
  • sleep不會釋放鎖,wait會釋放鎖
  • sleep使用interrupt()來喚醒,wait需要notify或者notifyAll來通知

Thread.sleep(long millis): 睡眠時不會釋放鎖。

public static void main(String[] args) throws InterruptedException {
    Object lock = new Object();

    new Thread(() -> {
        synchronized (lock) {
            for (int i = 0; i < 5; i++) {
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);
                try { Thread.sleep(1000); } catch (InterruptedException e) { }
            }
        }
    }).start();

    Thread.sleep(1000);

    new Thread(() -> {
        synchronized (lock) {
            for (int i = 0; i < 5; i++) {
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);
            }
        }
    }).start();
}

因main方法中Thread.sleep(1000)所以上面的線程Thread-0先被執行,當循環第一次時就會Thread.sleep(1000)睡眠,因爲sleep並不會釋放鎖,所以Thread-1得不到執行的機會,所以直到Thread-0執行完畢釋放鎖對象lock,Thread-1才能拿到鎖,然後執行Thread-1;

è¿éåå¾çæè¿°

 object.wait(long timeout): 會釋放鎖。

public class SleepWaitTest {
    public static void main(String[] args) throws InterruptedException {
        SleepWaitTest object = new SleepWaitTest();

        new Thread(() -> {
            synchronized (object) {
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t等待打印文件...");
                try {
                    object.wait(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t打印結束。。。");
            }
        }).start();

		 // 先上面的線程先執行
        Thread.sleep(1000);

        new Thread(() -> {
            synchronized (object) {
                for (int i = 0; i < 5; i++) {
                    System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "\t" + i);
                }
            }
        }).start();
    }
}

因main方法中有Thread.sleep(1000)所以上面的線程Thread-0肯定會被先執行,當Thread-0被執行時就拿到了object對象鎖,然後進入wait(5000)5秒鐘等待,此時wait釋放了鎖,然後Thread-1就拿到了鎖就執行線程體,Thread-1執行完後就釋放了鎖,當等待5秒後Thread-0就能再次獲取object鎖,這樣就繼續執行後面的代碼。wait方法是釋放鎖的,如果wait方法不釋放鎖那麼Thread-1是拿不到鎖也就沒有執行的機會的,事實是Thread-1得到了執行,所以說wait方法會釋放鎖。

è¿éåå¾çæè¿°

6.join()

讓當前線程加入父線程,加入後父線程會一直wait,直到子線程執行完畢後父線程才能執行。當我們調用某個線程的這個方法時,這個方法會掛起調用線程,直到被調用線程結束執行,調用線程纔會繼續執行。

將某個線程加入到當前線程中來,一般某個線程和當前線程依賴關係比較強,必須先等待某個線程執行完畢才能執行當前線程。一般在run()方法內使用。

join()方法:

 public final void join() throws InterruptedException {
        join(0);
}


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;
        }
    }
}


public final synchronized void join(long millis, int nanos) throws InterruptedException {

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

    if (nanos < 0 || nanos > 999999) {
        throw new IllegalArgumentException("nanosecond timeout value out of range");
    }

    if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
        millis++;
    }

    join(millis);
}

join() 和 join(long millis, int nanos) 最後都調用了 join(long millis)。

join(long millis, int nanos)和join(long millis)方法 都是synchronized。

join() 調用了join(0),從源碼可以看到join(0)不斷檢查當前線程是否處於Active狀態。

join() 和 sleep() 一樣,都可以被中斷(被中斷時,會拋出 InterrupptedException 異常);不同的是,join() 內部調用了wait(),會出讓鎖,而 sleep() 會一直保持鎖。

測試代碼:

 public class JoinTest {
    public static void main(String[] args) {
        new Thread(new ParentRunnable()).start();
    }
}

class ParentRunnable implements Runnable {
    @Override
    public void run() {
        // 線程處於new狀態
        Thread childThread = new Thread(new ChildRunable());
        // 線程處於runnable就緒狀態
        childThread.start();
        try {
            // 當調用join時,parent會等待child執行完畢後再繼續運行
            // 將某個線程加入到當前線程
            childThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        for (int i = 0; i < 5; i++) {
            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "父線程 running");
        }
    }
}

class ChildRunable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            try { Thread.sleep(1000); } catch (InterruptedException e) {}
            System.out.println(new Date() + "\t" + Thread.currentThread().getName() + "子線程 running");
        }
    }
}

程序進入主線程,運行Parent對應的線程,Parent的線程代碼分兩段,一段是啓動一個子線程,一段是Parent線程的線程體代碼,首先會將Child線程加入到Parent線程,join()方法會調用join(0)方法(join()方法是普通方法並沒有加鎖,join(0)會加鎖),join(0)會執行while(isAlive()) { wait(0);} 循環判斷線程是否處於活動狀態,如果是繼續wait(0)知道isAlive=false結束掉join(0), 從而結束掉join(), 最後回到Parent線程體中繼續執行其它代碼。

在Parent調用child.join()後,child子線程正常運行,Parent父線程會等待child子線程結束後再繼續運行。
è¿éåå¾çæè¿°

 

---------------------------------------------------end--------------------------------------------------------------

參考博文:https://blog.csdn.net/vbirdbest/article/details/81282163

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章