1. 線程方法
1.1 方法概覽
類.方法 | 簡介 |
---|---|
Thread.sleep() | 線程休眠,不釋放鎖 |
Thread.join() | 等待其它線程執行完畢 |
Thread.yield() | 放棄已經獲取到的CPU資源 |
Thread.currentThread() | 獲取當前線程的引用 |
Thread.start()/Thread.run() | 啓動線程和線程執行內容 |
Thread.interrupt() | 中斷線程 |
Thread.stop()/Thread.suspend()/Thread.resume() | 已廢棄 |
Object.wait()/Object.notify()/Object.notifyAll() | 線程等待和喚醒 |
1.2 wait()/notify()/notifyAll()
- 阻塞階段
執行了wait方法意味着當前線程進入阻塞階段,直到發生以下四種情況之一,該線程纔會被喚醒:- 另外一個線程調用這個對象的notify方法且剛好被喚醒的是本線程;
- 另外一個線程調用這個對象的notifyAll方法;
- 過了wait規定的超時時間,如果傳入0,就是永久等待;
- 線程自身調用了interrupt方法,wait方法因爲響應中斷被喚醒,進而拋出異常;
- 喚醒階段
- notify方法會喚醒單個等待線程,如果有多個等待線程,則隨機選取一個;
- notifyAll方法會喚醒所有等待線程;
1.3 wait()/notify()/notifyAll()方法詳解
下面展示wait和notify的基本用法:
public class WaitNotify {
private static Object object = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "開始執行");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "重新獲取到鎖");
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
object.notify();
System.out.println(Thread.currentThread().getName() + "調用了notify()");
}
}
});
t1.start();
Thread.sleep(200);
t2.start();
}
}
Thread-0開始執行
Thread-1調用了notify()
Thread-0重新獲取到鎖
可以看出,t1線程先啓動並執行了wait方法,釋放了鎖掛起,然後t2線程啓動,獲取到了鎖並執行了notify重新喚醒了t1線程,t1重新獲取到鎖,在掛起的位置繼續執行。
下面是wait和notifyAll的代碼演示:
public class WaitNotifyAll implements Runnable {
private static Object object = new Object();
@Override
public void run() {
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "獲取到鎖");
try {
object.wait();
System.out.println(Thread.currentThread().getName() + "重新獲取到鎖");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
WaitNotifyAll waitNotifyAll = new WaitNotifyAll();
Thread t1 = new Thread(waitNotifyAll);
Thread t2 = new Thread(waitNotifyAll);
t1.start();
t2.start();
Thread.sleep(200);
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (object) {
object.notifyAll();
// object.notify();
System.out.println(Thread.currentThread().getName() + "拿到了鎖並執行了notifyAll");
}
}
});
t3.start();
}
}
Thread-0獲取到鎖
Thread-1獲取到鎖
Thread-2拿到了鎖並執行了notifyAll
Thread-1重新獲取到鎖
Thread-0重新獲取到鎖
首先t1拿到了鎖並執行了wait方法掛起t1線程,然後t2拿到了鎖並執行了wait方法掛起t2線程,然後t3拿到了鎖並執行了notifyAll喚醒t1和t2線程,t2線程先重新獲取到了鎖,待執行完畢之後釋放了鎖,t2重新獲取到了鎖。
如果改用notify方法則隨機喚醒t1和t2線程其中的一個,如果t1被喚醒,則t2陷入了無盡的等待,整個進程永遠不會結束。
下面展示wait方法只能釋放調用wait方法的對象的鎖,不能釋放其他對象的鎖:
public class ReleaseOwnMonitor {
private static final Object lockA = new Object();
private static final Object lockB = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + "獲取到lockA");
synchronized (lockB) {
System.out.println(Thread.currentThread().getName() + "獲取到lockB");
try {
System.out.println(Thread.currentThread().getName() + "釋放了lockA");
lockA.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lockA) {
System.out.println(Thread.currentThread().getName() + "獲取到lockA");
System.out.println(Thread.currentThread().getName() + "正在嘗試獲取lockB...");
synchronized (lockB) {
//看看t2能不能獲取到lockB
System.out.println(Thread.currentThread().getName() + "獲取到lockB");
try {
lockA.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
t1.start();
Thread.sleep(500);
t2.start();
}
}
Thread-0獲取到lockA
Thread-0獲取到lockB
Thread-0釋放了lockA
Thread-1獲取到lockA
Thread-1正在嘗試獲取lockB...
結果表明,t2一直在嘗試獲取lockB,但是由於在t1中只有lockA執行了wait方法,釋放的也只是lockA的鎖,t1仍然持有lockB的鎖,所以t2獲取不到lockB的鎖。
1.4 wait()/notify()/notifyAll()特點和性質
- 用這些方法之前,當前線程必須首先獲取到monitor鎖
- 如果使用notify方法只會喚醒一個等待線程
- 都屬於Object類,所有的類繼承與Object,所有的對象都可以調用這些方法
- 類似功能的Condition
- 線程同時持有多個鎖,使用wait方法只釋放調用wait方法的對象的鎖
- 線程執行object.wait後,進入到WAITING狀態,而線程被喚醒後,因爲是另外一個線程持有相同的鎖才能執行object.notify方法進行喚醒,另外一個線程可能還沒有執行完,所以當前線程通常沒有立即獲取到monitor鎖,那麼當前線程會從WAIT狀態進入到BLOCKED狀態,搶到鎖後會進入到RUNNABLE狀態
- 發生異常,可以直接到TERMINATED狀態
1.5 sleep()方法詳解
sleep可以讓線程進入Waiting狀態,並且不佔用CPU資源,但是不釋放鎖,直到規定時間後再執行,休眠期間如果被中斷,會拋異常並清除中斷狀態。
public class SleepDontReleaseMonitor implements Runnable {
@Override
public void run() {
syn();
}
private synchronized void syn() {
System.out.println(Thread.currentThread().getName() + "獲取鎖");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "退出同步代碼塊");
}
public static void main(String[] args) {
SleepDontReleaseMonitor sleepDontReleaseMonitor = new SleepDontReleaseMonitor();
new Thread(sleepDontReleaseMonitor).start();
new Thread(sleepDontReleaseMonitor).start();
}
}
public class SleepDontReleaseLock implements Runnable {
private static final Lock lock = new ReentrantLock();
@Override
public void run() {
lock.lock();
System.out.println(Thread.currentThread().getName() + "獲取到了鎖");
try {
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName() + "甦醒");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
SleepDontReleaseLock sleepDontReleaseLock = new SleepDontReleaseLock();
new Thread(sleepDontReleaseLock).start();
new Thread(sleepDontReleaseLock).start();
}
}
可以看出,無論是synchronized和lock,執行sleep方法都不釋放鎖。
1.6 join()方法詳解
- 作用:因爲新的線程加入了我們,我們要等他執行完再出發
- 用法:main等待t1執行完畢,如下所示:
public class JoinBasic implements Runnable {
@Override
public void run() {
System.out.println("子線程開始執行");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子線程執行結束");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new JoinBasic());
thread.start();
thread.join();
System.out.println("主線程執行結束");
}
}
子線程開始執行
子線程執行結束
主線程執行結束
- join執行期間,是主線程等待子線程,所以我們看看join期間主線程是什麼狀態:
public class JoinMainState implements Runnable {
Thread mainThread = Thread.currentThread();
@Override
public void run() {
System.out.println("子線程開始執行");
try {
Thread.sleep(1000);
System.out.println(mainThread.getState());
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子線程執行結束");
}
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new JoinMainState());
t1.start();
t1.join();
}
}
子線程開始執行
WAITING
子線程執行結束
結果表明,主線程等待子線程join期間,主線程是WAITING狀態
- 因爲線程執行完畢之後會自動調用notifyAll方法,所以我們可以讓主線程等待,然後等子線程執行完後自動喚醒主線程,所以替代方法如下:
public class JoinPrinciple {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "子線程執行完畢");
}
});
thread.start();
System.out.println("等待所有子線程運行完畢");
// thread.join();
//給thread對象上鎖,thread對象執行完run方法後會自動調用thread.notifyAll()方法喚醒其他線程,即喚醒主線程
synchronized (thread) {
thread.wait();
}
System.out.println("所有子線程運行完畢");
}
}
等待所有子線程運行完畢
Thread-0子線程執行完畢
所有子線程運行完畢
synchronized (thread) {
thread.wait();
}
上面這段代碼也是join方法中的核心方法。
1.7 yield方法詳解
- 作用:釋放我的CPU時間片,線程狀態仍然是Runnable狀態,不會釋放鎖,不會阻塞,即便釋放了CPU時間片,下一個可能還是我
- yield和sleep區別:sleep期間,線程調度器不會把這個線程調度起來,而yield期間,立刻又可以被調度起來。
2. 線程屬性
2.1 屬性概覽
屬性名稱 | 用途 |
---|---|
ID | 標識不同的線程 |
Name | 自定義線程名稱 |
isDaemon | 是否是守護線程 |
Priority | 告訴線程調度器,用戶希望哪些線程多運行、哪些少運行,共10個等級,默認值是5 |
2.2 ID
從1開始,我們自己創建線程的ID早已不是2,因爲JVM在後開啓了很多其它子線程。
public class threadId {
public static void main(String[] args) {
Thread thread = new Thread();
System.out.println("子線程:" + thread.getId());
System.out.println("主線程:" + Thread.currentThread().getId());
}
}
子線程:11
主線程:1
2.3 Name
對於沒有指定名字的線程,會有默認名稱
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
private static int threadInitNumber;
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
2.2 isDaemon
給用戶線程提供服務,沒有用戶線程就沒有守護線程
-
線程類型默認繼承自父線程,守護線程創建的線程自動是守護線程,用戶線程同理
-
被誰啓動,通常,所有守護線程由JVM啓動,JVM啓動時,會有一個用戶線程,main函數
-
不影響JVM退出,所有用戶線程都結束,即便有守護線程,JVM也會退出
整體無區別,唯一的區別在於是否影響JVM的退出,守護線程不會影響。
2.2 Priority
10個級別,默認5
/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY = 1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY = 5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY = 10;
程序設計不應依賴於優先級,跟操作系統息息相關,不可靠
- 因爲不同操作系統不一樣
- 優先級會被操作系統改變