1、方法概覽
類 | 方法名 | 簡介 |
---|---|---|
Thread | sleep相關 | 本表格的“相關”,指的是重載方法,如sleep有多個重載方法,但實際作用大同小異 |
. | join | 主線程等待ThreaA執行完畢(ThreadA.join()) |
. | yield相關 | 放棄已經獲取到的CPU資源 |
. | currentThread | 獲取當前執行線程的引用 |
. | start,run相關 | 啓動線程相關 |
. | interrupt相關 | 中斷線程 |
. | stop(),suspend(),resuem()相關 | 已廢棄 |
Object | wait/notify/notifyAll相關 | 讓線程暫時休息和喚醒 |
2、wait,notify,notifyAll方法詳解
2.1、作用、用法:阻塞階段、喚醒階段、遇到中斷
(1)阻塞階段
線程調用wait()方法,則該線程進入到阻塞狀態,直到以下4種情況之一發生時,纔會被喚醒
- 另一個線程調用這個對象的notify()方法且剛好被喚醒的是本線程
- 另一個線程調用這個對象的notifyAll()方法且剛好被喚醒的是本線程
- 過了wait(long timeout)規定的超時時間,如果傳入0就是永久等待
- 線程自身調用了interrupt
(2)喚醒階段
- notify會喚起單個在等待某對象monitor的線程,如果有多個線程在等待,則只會喚起其中隨機的一個
- notifyAll會將所有等待的線程都喚起,而喚起後具體哪個線程會獲得monitor,則看操作系統的調度
- notify必須在synchronized中調用,否則會拋出異常
java.lang.IllegalMonitorStateException
at java.lang.Object.notify(Native Method)
at BlockedWaitingTimedWaiting.run(BlockedWaitingTimedWaiting.java:37)
at java.lang.Thread.run(Thread.java:748)
(3)遇到中斷
- 假設線程執行了wait(),在此期間被中斷,則會拋出interruptException,同時釋放已經獲取到的monitor
2.2、代碼演示:4種情況
(1)普通用法
/**
* Wait
*
* @author venlenter
* @Description: 展示wait和notify的基本用法
* 1. 研究代碼執行順序
* 2. 證明wait釋放鎖
* @since unknown, 2020-04-09
*/
public class Wait {
public static Object object = new Object();
static class Thread1 extends Thread {
@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() + "獲取到了鎖");
}
}
}
static class Thread2 extends Thread {
@Override
public void run() {
synchronized (object) {
object.notify();
System.out.println("線程" + Thread.currentThread().getName() + "調用了notify()");
}
}
}
public static void main(String[] args) {
Thread thread1 = new Thread1();
Thread thread2 = new Thread2();
thread1.start();
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
thread2.start();
}
}
//輸出結果
Thread-0開始執行了
線程Thread-1調用了notify()
線程Thread-0獲取到了鎖
//解析
①Thread-0進入Thread1類synchronized代碼塊,獲得鎖,輸出“Thread-0開始執行”
②然後Thread-0執行object.wait(),釋放了鎖
③Thread-1獲得鎖,進入Thread2類synchronized,執行object.notify(),輸出“線程Thread-1調用了notify()”,同時Thread-0也被喚醒了
④Thread-0回到object.wait()的位置,執行下面的代碼邏輯,輸出“線程Thread-0獲取到了鎖”
(2)notify和notifyAll展示
/**
* WaitNotifyAll
*
* @author venlenter
* @Description: 3個線程,線程1和線程2首先被阻塞,線程3喚醒它們。notify,notifyAll
* start先執行不代表線程先啓動
* @since unknown, 2020-04-11
*/
public class WaitNotifyAll implements Runnable{
private static final Object resourceA = new Object();
@Override
public void run() {
synchronized(resourceA) {
System.out.println(Thread.currentThread().getName() + " get resourceA lock");
try {
System.out.println(Thread.currentThread().getName() + " wait to start");
resourceA.wait();
System.out.println(Thread.currentThread().getName() + "'s waiting end");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Runnable r = new WaitNotifyAll();
Thread threadA = new Thread(r);
Thread threadB = new Thread(r);
Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
resourceA.notifyAll();
//resourceA.notify();
System.out.println("ThreadC notifyed.");
}
}
});
threadA.start();
threadB.start();
Thread.sleep(200);
threadC.start();
}
}
//輸出結果
Thread-0 get resourceA lock
Thread-0 wait to start
Thread-1 get resourceA lock
Thread-1 wait to start
ThreadC notifyed.
Thread-1's waiting end
Thread-0's waiting end
(3)只釋放當前monitor展示
/**
* WaitNotifyReleaseOwnMonitor
*
* @author venlenter
* @Description: 證明wait只釋放當前的那把鎖
* @since unknown, 2020-04-11
*/
public class WaitNotifyReleaseOwnMonitor {
private static volatile Object resourceA = new Object();
private static volatile Object resourceB = new Object();
public static void main(String[] args) {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
System.out.println("ThreadA got resourceA lock.");
synchronized (resourceB) {
System.out.println("ThreadA got resourceB lock.");
try {
System.out.println("ThreadA releases resourceA lock.");
resourceA.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resourceA) {
System.out.println("ThreadB got resourceA lock.");
System.out.println("ThreadB tries to resourceB lock.");
synchronized (resourceB) {
System.out.println("ThreadB got resourceB lock.");
}
}
}
});
thread1.start();
thread2.start();
}
}
//輸出結果
ThreadA got resourceA lock.
ThreadA got resourceB lock.
ThreadA releases resourceA lock.
ThreadB got resourceA lock.
ThreadB tries to resourceB lock.
//沒有打印ThreadB got resourceB lock.(因爲只調用了A.wait,只釋放了lockA,B還沒佔用着)
2.3、特點、性質
- 使用的時候必須先擁有monitor(synchronized鎖)
- notify只能喚醒其中一個
- 屬於Object類
2.4、原理
2.4.1 手寫生產者消費者設計模式
- 什麼是生產者消費者模式
/**
* ProducerConsumerModel
*
* @author venlenter
* @Description: 用wait/notify來實現
* @since unknown, 2020-04-11
*/
public class ProducerConsumerModel {
public static void main(String[] args) {
EventStorage eventStorage = new EventStorage();
Producer producer = new Producer(eventStorage);
Consumer consumer = new Consumer(eventStorage);
new Thread(producer).start();
new Thread(consumer).start();
}
}
class Producer implements Runnable {
private EventStorage storage;
public Producer(EventStorage storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
storage.put();
}
}
}
class Consumer implements Runnable {
private EventStorage storage;
public Consumer(EventStorage storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
storage.take();
}
}
}
class EventStorage {
private int maxSize;
private LinkedList<Date> storage;
public EventStorage() {
maxSize = 10;
storage = new LinkedList<>();
}
public synchronized void put() {
while (storage.size() == maxSize) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
storage.add(new Date());
System.out.println("倉庫中已經有" + storage.size() + "個產品。");
notify();
}
public synchronized void take() {
while (storage.size() == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("拿到了" + storage.poll() + ",現在倉庫還剩下" + storage.size());
notify();
}
}
//輸出結果
倉庫中已經有1個產品。
倉庫中已經有2個產品。
倉庫中已經有3個產品。
倉庫中已經有4個產品。
倉庫中已經有5個產品。
倉庫中已經有6個產品。
倉庫中已經有7個產品。
倉庫中已經有8個產品。
倉庫中已經有9個產品。
倉庫中已經有10個產品。
拿到了Sun Apr 12 11:07:50 CST 2020,現在倉庫還剩下9
拿到了Sun Apr 12 11:07:50 CST 2020,現在倉庫還剩下8
拿到了Sun Apr 12 11:07:50 CST 2020,現在倉庫還剩下7
拿到了Sun Apr 12 11:07:50 CST 2020,現在倉庫還剩下6
拿到了Sun Apr 12 11:07:50 CST 2020,現在倉庫還剩下5
拿到了Sun Apr 12 11:07:50 CST 2020,現在倉庫還剩下4
拿到了Sun Apr 12 11:07:50 CST 2020,現在倉庫還剩下3
拿到了Sun Apr 12 11:07:50 CST 2020,現在倉庫還剩下2
拿到了Sun Apr 12 11:07:50 CST 2020,現在倉庫還剩下1
拿到了Sun Apr 12 11:07:50 CST 2020,現在倉庫還剩下0
倉庫中已經有1個產品。
拿到了Sun Apr 12 11:07:50 CST 2020,現在倉庫還剩下0
2.5、注意點
2.6、常見面試問題
2.6.1 兩個線程交替打印0~100的奇偶數
- 基本方式:用synchronized關鍵字實現
/**
* WaitNotifyPrintOddEvenSyn
*
* @author venlenter
* @Description: 兩個線程交替打印0~100的奇偶數,用synchronized關鍵字實現
* @since unknown, 2020-04-12
*/
public class WaitNotifyPrintOddEvenSyn {
public static int count = 0;
public static final Object lock = new Object();
//新建2個線程
//1個只處理偶數,第二個只處理奇數(用位運算)
//用synchronized來通信
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while (count < 100) {
synchronized (lock) {
if ((count & 1) == 0) {
System.out.println((Thread.currentThread().getName() + ":" + count++));
}
}
}
}
}, "偶數").start();
new Thread(new Runnable() {
@Override
public void run() {
while (count < 100) {
synchronized (lock) {
if ((count & 1) == 1) {
System.out.println(Thread.currentThread().getName() + ":" + count++);
}
}
}
}
}, "奇數").start();
}
}
//輸出結果
//輸出正確,但是實際上如果thread1(偶數線程)一直支持lock,會有不斷循環做無效的操作
偶數:0
奇數:1
偶數:2
奇數:3
...
奇數:99
偶數:100
- 更好的方法:wait/notify
/**
* WaitNotifyPrintOddEvenWait
*
* @author venlenter
* @Description: 兩個線程交替打印0~100的奇偶數,用wait和notify
* @since unknown, 2020-04-12
*/
public class WaitNotifyPrintOddEvenWait {
private static int count = 0;
private static Object lock = new Object();
//1. 拿到鎖,我們就打印
//2. 打印完,喚醒其他線程,自己就休眠
static class TurningRunner implements Runnable {
@Override
public void run() {
while (count <= 100) {
synchronized (lock) {
//拿到鎖就打印
System.out.println(Thread.currentThread().getName() + ":" + count++);
lock.notify();
if (count <= 100) {
try {
//如果任務還沒結束,就讓出當前線程,並休眠
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(new TurningRunner(),"偶數").start();
Thread.sleep(100);
new Thread(new TurningRunner(),"奇數").start();
}
}
//輸出結果
偶數:0
奇數:1
偶數:2
奇數:3
...
奇數:99
偶數:100
2.6.2 手寫生產者消費者設計模式
2.6.3 爲什麼wait()需要在同步代碼塊內使用,而sleep()不需要
- 正常邏輯是先執行wait,後續在執行notify喚醒。如果wait/notify不放同步代碼塊,執行wait的時候,線程切換去執行其他任務如notify,導致notify先於wait,就會導致後續切回wait的時候,一直阻塞着,無法釋放,導致死鎖。
- 而sleep是針對本身的當前線程的,不影響
2.6.4 爲什麼線程通信的方法wait(),notify()和notifyAll被定義在Object類裏?而sleep定義在Thread類裏?
- wait、notify、notifyAll是鎖級別的操作,屬於Object對象的,而線程實際上是可以持有多把鎖的,如果把wait定義到Thread裏面,就無法做到這麼靈活的控制了
2.6.5 wait方法是屬於Object對象的,那調用Thread.wait會怎麼樣?
- Thread線程退出的時候,會自動調用notify,這可能不是我們所期望的,所以最好不要用Thread.wait
2.6.6 如何選擇notify還是notifyAll?
- 參考2.2、代碼演示(2)notify和notifyAll展示
- notify是喚起一個線程,選擇哪個是隨機的。而notifyAll是喚起所有線程,然後這些線程再次搶去奪鎖
2.6.7 notifyAll之後所有的線程都會再次搶奪鎖,如果某線程搶奪失敗怎麼辦?
- 實質就跟初始狀態一樣,多個線程搶奪鎖,搶不到的線程就等待,等待上一個線程釋放鎖
2.6.8 用suspend()和resume()來阻塞線程可以嗎?爲什麼?
- 這2個方法由於不安全,已經被棄用了。最好還是使用wait和notify
3、sleep方法詳解
3.1 作用:我只想讓線程在預期的時間執行,其他時候不要佔用CPU資源
3.2 不釋放鎖
- 包括synchronized和lock
/**
* SleepDontReleaseMonitor
*
* @author venlenter
* @Description: 展示線程sleep的時候不釋放synchronized的monitor,等sleep時間到了以後,正常結束後才釋放鎖
* @since unknown, 2020-04-15
*/
public class SleepDontReleaseMonitor implements Runnable{
public static void main(String[] args) {
SleepDontReleaseMonitor sleepDontReleaseMonitor = new SleepDontReleaseMonitor();
new Thread(sleepDontReleaseMonitor).start();
new Thread(sleepDontReleaseMonitor).start();
}
@Override
public void run() {
syn();
}
private synchronized void syn() {
System.out.println("線程" + Thread.currentThread().getName() + "獲取到了monitor");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程" + Thread.currentThread().getName() + "退出了同步代碼塊");
}
}
//輸出結果
線程Thread-0獲取到了monitor
線程Thread-0退出了同步代碼塊(5s後出現)
線程Thread-1獲取到了monitor
線程Thread-1退出了同步代碼塊(5s後出現)
/**
* SleepDontReleaseLock
*
* @author venlenter
* @Description: 演示sleep不釋放lock(lock需要手動釋放)
* @since unknown, 2020-04-15
*/
public class SleepDontReleaseLock implements Runnable {
private static final Lock lock = new ReentrantLock();
@Override
public void run() {
lock.lock();
System.out.println("線程" + Thread.currentThread().getName() + "獲取到了lock");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
System.out.println("線程" + Thread.currentThread().getName() + "釋放了lock");
}
public static void main(String[] args) {
SleepDontReleaseLock sleepDontReleaseLock = new SleepDontReleaseLock();
new Thread(sleepDontReleaseLock).start();
new Thread(sleepDontReleaseLock).start();
}
}
//輸出結果
線程Thread-0獲取到了lock
線程Thread-0釋放了lock(5s後)
線程Thread-1獲取到了lock
線程Thread-1釋放了lock(5s後)
- 和wait不同
3.3 sleep方法響應中斷
- 拋出InterruptedException
- 清除中斷狀態
/**
* SleepInterrupted
*
* @author venlenter
* @Description: 每隔1s輸出當前時間,被中斷,觀察
* Thread.sleep()
* TimeUnit.SECONDS.sleep()
* @since unknown, 2020-04-15
*/
public class SleepInterrupted implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(new Date());
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
System.out.println("我被中斷了");
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new SleepInterrupted());
thread.start();
Thread.sleep(6500);
thread.interrupt();
}
}
//輸出結果
Wed Apr 15 23:09:55 CST 2020
Wed Apr 15 23:09:56 CST 2020
Wed Apr 15 23:09:57 CST 2020
Wed Apr 15 23:09:58 CST 2020
Wed Apr 15 23:09:59 CST 2020
Wed Apr 15 23:10:00 CST 2020
Wed Apr 15 23:10:01 CST 2020
我被中斷了
java.lang.InterruptedException: sleep interrupted
Wed Apr 15 23:10:01 CST 2020
at java.lang.Thread.sleep(Native Method)
at java.lang.Thread.sleep(Thread.java:340)
at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
at ConcurrenceFolder.mooc.threadConcurrencyCore.threadobjectclasscommonmethods.SleepInterrupted.run(SleepInterrupted.java:21)
at java.lang.Thread.run(Thread.java:748)
Wed Apr 15 23:10:02 CST 2020
Wed Apr 15 23:10:03 CST 2020
3.4 sleep總結
- sleep方法可以讓線程進入Waiting狀態,並且不佔用CPU資源
- 但是不釋放鎖,直到規定時間後再執行
- 休眠期間如果被中斷,會拋出異常並清除中斷狀態
3.5 sleep常見面試問題
wait/notify、sleep異同(方法屬於哪個對象?線程狀態怎麼切換?)
(1)相同
- 都會阻塞
- 都可以響應中斷
外層執行thread.interrupt()
try {
wait();
Thread.sleep();
} catch (InterruptedException e) {
e.printStackTrace();
}
(2)不同
- wait/notify需要在synchronized方法中,而sleep不需要
- 釋放鎖:wait會釋放鎖,而sleep不釋放鎖
- 指定時間:sleep必須傳參時間,而wait有多個構造方法,不傳時間則直到自己被喚醒
- 所屬類:wait/notify是Object方法,sleep是Thread類的方法
4、join方法
4.1 作用:因爲新的線程加入了“我們”,所以“我們”要等他執行完再出發
4.2 用法:(在main方法中thread1.join)main等待thread1執行完畢,注意誰等誰(父等待子)
4.3 三個例子
- 普通用法
/**
* Join
*
* @author venlenter
* @Description: 演示join,注意語句輸出順序,會變化
* @since unknown, 2020-04-15
*/
public class Join {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "執行完畢");
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "執行完畢");
}
});
thread1.start();
thread2.start();
System.out.println("開始等待子線程運行完畢");
thread1.join();
thread2.join();
System.out.println("所有子線程執行完畢");
}
}
//輸出結果
開始等待子線程運行完畢
Thread-0執行完畢
Thread-1執行完畢
所有子線程執行完畢
- 遇到中斷
/**
* JoinInterrupt
*
* @author venlenter
* @Description: 演示join期間被中斷的效果
* @since unknown, 2020-04-21
*/
public class JoinInterrupt {
public static void main(String[] args) {
Thread mainThread = Thread.currentThread();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
mainThread.interrupt();
Thread.sleep(5000);
System.out.println("Thread1 finished.");
} catch (InterruptedException e) {
System.out.println("子線程中斷");
}
}
});
thread1.start();
System.out.println("等待子線程運行完畢");
try {
thread1.join();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "主線程中斷了");
thread1.interrupt();
}
System.out.println("子線程已運行完畢");
}
}
//輸出結果
等待子線程運行完畢
main主線程中斷了
子線程已運行完畢
子線程中斷
- 在join期間,線程到底是什麼狀態?:Waiting
/**
* JoinThreadState
*
* @author venlenter
* @Description: 先join再mainThread.getState()
* 通過debugger看線程join前後狀態的對比
* @since unknown, 2020-04-22
*/
public class JoinThreadState {
public static void main(String[] args) throws InterruptedException {
Thread mainThread = Thread.currentThread();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(3000);
System.out.println(mainThread.getState());
System.out.println("Thread-0運行結束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
System.out.println("等待子線程運行完畢");
thread.join();
System.out.println("子線程運行完畢");
}
}
//輸出結果
等待子線程運行完畢
WAITING
Thread-0運行結束
子線程運行完畢
4.4 可以使用封裝工具類:CountDownLatch或CyclicBarrier
4.5 join原理
- 源碼
(1)thread.join();
(2)
public final void join() throws InterruptedException {
join(0);
}
(3)
public final synchronized void join(long millis)
throws InterruptedException {
...
if (millis == 0) {
while (isAlive()) {
wait(0);
}
}
- 分析:線程在run執行完成後,JVM底層會自動調用一個notifyAll喚醒,所以即使在join()內沒有notify顯示調用,執行完run()後,也會喚醒
- 等價
// thread.join(); 等價於下面synchronized的代碼
synchronized (thread) {
thread.wait();
}
4.6 常見面試問題
- 在join期間,線程處於哪種線程狀態?Waiting
5、yield方法
- 作用:釋放我的CPU時間片。線程狀態仍然是RUNNABLE,不釋放鎖,也不阻塞
- 定位:JVM不保證遵循yield邏輯
- yield和sleep區別:yield隨時可能再次被調度
6、獲取當前執行線程的引用:Thread.currentThread()方法
- 同一個方法,不同線程會打印出各自線程的名稱
/**
* CurrentThread
*
* @author venlenter
* @Description: 演示打印majn, Thread-0, Thread-1
* @since unknown, 2020-04-22
*/
public class CurrentThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
public static void main(String[] args) {
new CurrentThread().run();
new Thread(new CurrentThread()).start();
new Thread(new CurrentThread()).start();
}
}
//輸出
main
Thread-0
Thread-1
筆記來源:慕課網悟空老師視頻《Java併發核心知識體系精講》