- 為什么線程通信的方法wait()拇颅,notify()和notifyAll()被定義在Object類里校读?而sleep定義在Thread類里再登?
- 用3種方式實(shí)現(xiàn)生產(chǎn)者模式
- Join和sleep和wait期間線程的狀態(tài)分別是什么耕陷?為什么谜慌?
1. wait,notify,notifyAll方法
作用字旭、用法:阻塞階段对湃、喚醒階段、遇到中斷
1.1阻塞階段
- 另一個(gè)線程調(diào)用該對(duì)象的notify()方法且剛好被喚醒的是本線程遗淳。
- 另一個(gè)線程調(diào)用該對(duì)象的notifyAll()方法
- 過來wait(long timeout)規(guī)定的超時(shí)時(shí)間拍柒,如果傳入0就是永久等待。
- 線程自身調(diào)用了interrupt()屈暗。
wait,notify,notifyAll作用拆讯、方法
- 阻塞階段
- 喚醒階段
- 遇到中斷
wait、notify
/**
* 描述: 展示wait和notify的基本用法 1. 研究代碼執(zhí)行順序 2. 證明wait釋放鎖
*/
public class Wait {
public static Object object = new Object();
public static void main(String[] args) throws InterruptedException {
Thread1 thread1 = new Thread1();
Thread2 thread2 = new Thread2();
thread1.start();
Thread.sleep(200);
thread2.start();
}
static class Thread1 extends Thread {
@Override
public void run() {
synchronized (object) {
System.out.println(Thread.currentThread().getName() + "開始執(zhí)行了");
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() + "調(diào)用了notify()");
}
}
}
}
Thread-0開始執(zhí)行了
線程Thread-1調(diào)用了notify()
線程Thread-0獲取到了鎖种呐。
notifyAll
/**
* 描述: 3個(gè)線程,線程1和線程2首先被阻塞弃甥,線程3喚醒它們爽室。notify, notifyAll。 start先執(zhí)行不代表線程先啟動(dòng)淆攻。
*/
public class WaitNotifyAll implements Runnable {
private static final Object resourceA = new Object();
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 notified.");
}
}
});
threadA.start();
threadB.start();
Thread.sleep(200);
threadC.start();
}
@Override
public void run() {
synchronized (resourceA) {
System.out.println(Thread.currentThread().getName()+" got resourceA lock.");
try {
System.out.println(Thread.currentThread().getName()+" waits to start.");
resourceA.wait();
System.out.println(Thread.currentThread().getName()+"'s waiting to end.");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
Thread-0 got resourceA lock.
Thread-0 waits to start.
Thread-1 got resourceA lock.
Thread-1 waits to start.
ThreadC notified.
Thread-1's waiting to end.
Thread-0's waiting to end.
如果將resourceA.notifyAll() 改為resourceA.notify() 輸出結(jié)果
Thread-0 got resourceA lock.
Thread-0 waits to start.
Thread-1 got resourceA lock.
Thread-1 waits to start.
ThreadC notified.
Thread-0's waiting to end.
wait只釋放當(dāng)前的那把鎖
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.
2.生產(chǎn)者消費(fèi)者設(shè)計(jì)模式
產(chǎn)生數(shù)據(jù)的模塊阔墩,就形象地稱為生產(chǎn)者;而處理數(shù)據(jù)的模塊瓶珊,就稱為消費(fèi)者啸箫。
單單抽象出生產(chǎn)者和消費(fèi)者,還夠不上是生產(chǎn)者/消費(fèi)者模式艰毒。該模式還需要有一個(gè)緩沖區(qū)處于生產(chǎn)者和消費(fèi)者之間筐高,作為一個(gè)中介。生產(chǎn)者把數(shù)據(jù)放入緩沖區(qū)丑瞧,而消費(fèi)者從緩沖區(qū)取出數(shù)據(jù)柑土。
為什么要使用生產(chǎn)者和消費(fèi)者模式
- 解耦:
假設(shè)生產(chǎn)者和消費(fèi)者分別是兩個(gè)類。如果讓生產(chǎn)者直接調(diào)用消費(fèi)者的某個(gè)方法绊汹,那么生產(chǎn)者對(duì)于消費(fèi)者就會(huì)產(chǎn)生依賴(也就是耦合)稽屏。將來如果消費(fèi)者的代碼發(fā)生變化,可能會(huì)影響到生產(chǎn)者西乖。而如果兩者都依賴于某個(gè)緩沖區(qū)狐榔,兩者之間不直接依賴坛增,耦合也就相應(yīng)降低了。 - 支持并發(fā):
生產(chǎn)者直接調(diào)用消費(fèi)者的某個(gè)方法薄腻,還有另一個(gè)弊端收捣。由于函數(shù)調(diào)用是同步的(或者叫阻塞的),在消費(fèi)者的方法沒有返回之前庵楷,生產(chǎn)者只好一直等在那邊罢艾。萬一消費(fèi)者處理數(shù)據(jù)很慢,生產(chǎn)者就會(huì)白白糟蹋大好時(shí)光尽纽。
使用了生產(chǎn)者/消費(fèi)者模式之后咐蚯,生產(chǎn)者和消費(fèi)者可以是兩個(gè)獨(dú)立的并發(fā)主體(常見并發(fā)類型有進(jìn)程和線程兩種)。生產(chǎn)者把制造出來的數(shù)據(jù)往緩沖區(qū)一丟弄贿,就可以再去生產(chǎn)下一個(gè)數(shù)據(jù)春锋。基本上不用依賴消費(fèi)者的處理速度差凹。其實(shí)當(dāng)初這個(gè)模式期奔,主要就是用來處理并發(fā)問題的。 - 支持忙閑不均:
緩沖區(qū)還有另一個(gè)好處直奋。如果制造數(shù)據(jù)的速度時(shí)快時(shí)慢能庆,緩沖區(qū)的好處就體現(xiàn)出來了。當(dāng)數(shù)據(jù)制造快的時(shí)候脚线,消費(fèi)者來不及處理搁胆,未處理的數(shù)據(jù)可以暫時(shí)存在緩沖區(qū)中。等生產(chǎn)者的制造速度慢下來邮绿,消費(fèi)者再慢慢處理掉渠旁。
環(huán)形緩沖區(qū)(減少了內(nèi)存分配的開銷)
雙緩沖區(qū)(減少了同步/互斥的開銷)
/**
* 描述: 用wait/notify來實(shí)現(xiàn)生產(chǎn)者消費(fèi)者模式
*/
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 final 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 final 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 final int maxSize;
private final 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() + "個(gè)產(chǎn)品。");
notify();
}
public synchronized void take() {
while (storage.size() == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("拿到了" + storage.poll() + "船逮,現(xiàn)在倉庫還剩下" + storage.size());
notify();
}
}
倉庫里有了1個(gè)產(chǎn)品顾腊。
倉庫里有了2個(gè)產(chǎn)品。
倉庫里有了3個(gè)產(chǎn)品挖胃。
倉庫里有了4個(gè)產(chǎn)品杂靶。
倉庫里有了5個(gè)產(chǎn)品。
倉庫里有了6個(gè)產(chǎn)品酱鸭。
倉庫里有了7個(gè)產(chǎn)品吗垮。
倉庫里有了8個(gè)產(chǎn)品。
倉庫里有了9個(gè)產(chǎn)品凹髓。
倉庫里有了10個(gè)產(chǎn)品烁登。
拿到了Wed Dec 23 11:44:11 CST 2020,現(xiàn)在倉庫還剩下9
拿到了Wed Dec 23 11:44:11 CST 2020蔚舀,現(xiàn)在倉庫還剩下8
拿到了Wed Dec 23 11:44:11 CST 2020饵沧,現(xiàn)在倉庫還剩下7
拿到了Wed Dec 23 11:44:11 CST 2020锨络,現(xiàn)在倉庫還剩下6
拿到了Wed Dec 23 11:44:11 CST 2020,現(xiàn)在倉庫還剩下5
拿到了Wed Dec 23 11:44:11 CST 2020狼牺,現(xiàn)在倉庫還剩下4
拿到了Wed Dec 23 11:44:11 CST 2020羡儿,現(xiàn)在倉庫還剩下3
拿到了Wed Dec 23 11:44:11 CST 2020,現(xiàn)在倉庫還剩下2
拿到了Wed Dec 23 11:44:11 CST 2020锁右,現(xiàn)在倉庫還剩下1
拿到了Wed Dec 23 11:44:11 CST 2020失受,現(xiàn)在倉庫還剩下0
倉庫里有了1個(gè)產(chǎn)品讶泰。
倉庫里有了2個(gè)產(chǎn)品咏瑟。
...省略
為什么wait()方法要放在同步塊中
防止出現(xiàn)Lost Wake-Up Problem (防止一直wait中)
https://blog.csdn.net/zl1zl2zl3/article/details/89236983
https://www.cnblogs.com/xiohao/p/7118102.html
為什么線程通信的方法wait(),notify()和notifyAll()被定義在Object類里
- 這些方法存在于同步中痪署;
- 使用這些方法必須標(biāo)識(shí)同步所屬的鎖码泞;
- 鎖可以是任意對(duì)象,所以任意對(duì)象調(diào)用方法一定定義在Object類中狼犯。
- 一個(gè)線程可以加多把鎖
3. sleep方法不釋放鎖
- 包括synchronized和lock
- 和wait不同
/**
* 展示線程sleep的時(shí)候不釋放synchronized的monitor余寥,等sleep時(shí)間到了以后,正常結(jié)束后才釋放鎖
*/
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-1獲取到了monitor宋舷。
線程Thread-1退出了同步代碼塊
線程Thread-0獲取到了monitor。
線程Thread-0退出了同步代碼塊
public class SleepDontReleaseLock implements Runnable {
private static final Lock lock = new ReentrantLock();
public static void main(String[] args) {
SleepDontReleaseLock sleepDontReleaseLock = new SleepDontReleaseLock();
new Thread(sleepDontReleaseLock).start();
new Thread(sleepDontReleaseLock).start();
}
@Override
public void run() {
lock.lock();
System.out.println("線程" + Thread.currentThread().getName() + "獲取到了鎖");
try {
Thread.sleep(5000);
System.out.println("線程" + Thread.currentThread().getName() + "已經(jīng)蘇醒");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
線程Thread-0獲取到了鎖
線程Thread-0已經(jīng)蘇醒
線程Thread-1獲取到了鎖
線程Thread-1已經(jīng)蘇醒
3.1 sleep方法響應(yīng)中斷
- 拋出InterruptedException
- 清除中斷狀態(tài)
public class SleepInterrupted implements Runnable {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new SleepInterrupted());
thread.start();
Thread.sleep(6500);
thread.interrupt();
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(new Date());
try {
TimeUnit.HOURS.sleep(3);
TimeUnit.MINUTES.sleep(25);
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
System.out.println("我被中斷了瓢姻!");
e.printStackTrace();
}
}
}
}
Wed Dec 23 16:24:15 CST 2020
我被中斷了祝蝠!
Wed Dec 23 16:24:22 CST 2020
java.lang.InterruptedException: sleep interrupted
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 com.kpioneer.thread.threadcoreknowledge.threadobjectclasscommonmethods.SleepInterrupted.run(SleepInterrupted.java:24)
at java.lang.Thread.run(Thread.java:748)
總結(jié):sleep方法可以讓線程進(jìn)入到Waiting狀態(tài),并且不占用CPU資源幻碱,但是不釋放鎖绎狭,直到規(guī)定時(shí)間后再執(zhí)行,休眠期間如果被中斷褥傍,會(huì)拋出異常并清除中斷狀態(tài)儡嘶。
wait/notify 和 sleep 方法的異同
相同點(diǎn):
- 它們都可以讓線程阻塞。
- 它們都可以響應(yīng) interrupt 中斷:在等待的過程中如果收到中斷信號(hào)恍风,都可以進(jìn)行響應(yīng)蹦狂,并拋出 InterruptedException 異常。
不同點(diǎn):
- wait 方法必須在 synchronized 保護(hù)的代碼中使用朋贬,而 sleep 方法并沒有這個(gè)要求凯楔。
- 在同步代碼中執(zhí)行 sleep 方法時(shí),并不會(huì)釋放 monitor 鎖兄世,但執(zhí)行 wait 方法時(shí)會(huì)主動(dòng)釋放 monitor 鎖啼辣。
- sleep 方法中會(huì)要求必須定義一個(gè)時(shí)間,時(shí)間到期后會(huì)主動(dòng)恢復(fù)御滩,而對(duì)于沒有參數(shù)的 wait 方法而言鸥拧,意味著永久等待党远,直到被中斷或被喚醒才能恢復(fù)富弦,它并不會(huì)主動(dòng)恢復(fù)。
- wait/notify 是 Object 類的方法腕柜,而 sleep 是 Thread 類的方法。
4. join方法
4.1 join方法作用盏缤、用法
作用:因?yàn)樾碌木€程加入了我們,所以我們需要等他執(zhí)行完再出發(fā)
用法:main等待thread1執(zhí)行完畢唉铜,注意誰等誰
4.2 join普通用法
public class Join {
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() + "執(zhí)行完畢");
}
});
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() + "執(zhí)行完畢");
}
});
thread.start();
thread2.start();
System.out.println("開始等待子線程運(yùn)行完畢");
thread.join();
thread2.join();
System.out.println("所有子線程執(zhí)行完畢");
}
}
開始等待子線程運(yùn)行完畢
Thread-1執(zhí)行完畢
Thread-0執(zhí)行完畢
所有子線程執(zhí)行完畢
4.3 join遇到中斷
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("等待子線程運(yùn)行完畢");
try {
thread1.join();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "主線程中斷了");
}
System.out.println("子線程已運(yùn)行完畢");
}
}
等待子線程運(yùn)行完畢
main主線程中斷了
子線程已運(yùn)行完畢
Thread1 finished.
注意: 發(fā)現(xiàn) 子線程已運(yùn)行完畢又打印了Thread1 finished. 說明子線程 并沒有運(yùn)行完畢
優(yōu)化:在異常中指定子線程中斷thread1.interrupt()
try {
thread1.join();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "主線程中斷了");
thread1.interrupt();
}
樹池
等待子線程運(yùn)行完畢
main主線程中斷了
子線程已運(yùn)行完畢
子線程中斷
Thread1 finished. 沒有打印符合結(jié)果
4.4 在join期間潭流,線程到底是什么狀態(tài)
/**
* 描述: 先join再mainThread.getState()
* 通過debugger看線程join前后狀態(tài)的對(duì)比
*/
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運(yùn)行結(jié)束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
System.out.println("等待子線程運(yùn)行完畢");
thread.join();
System.out.println("子線程運(yùn)行完畢");
}
}
等待子線程運(yùn)行完畢
WAITING
Thread-0運(yùn)行結(jié)束
子線程運(yùn)行完畢
4.5 join等價(jià)實(shí)現(xiàn)
/**
* 描述: 通過講解join原理,分析出join的代替寫法
*/
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() + "執(zhí)行完畢");
}
});
thread.start();
System.out.println("開始等待子線程運(yùn)行完畢");
// thread.join();
synchronized (thread) {
thread.wait();
}
System.out.println("所有子線程執(zhí)行完畢");
}
}
開始等待子線程運(yùn)行完畢
Thread-0執(zhí)行完畢
所有子線程執(zhí)行完畢
5 yield方法詳解
作用:釋放我的CPU時(shí)間片
定位:JVM不保證遵循
yield 線程可能再次被調(diào)度