在Java最早期的時候,常常採用suspend()和resume()方法對線程進行阻塞和喚醒,但是現在不再推薦使用了,是因爲:
suspend()方法在導致線程阻塞的過程中,不會釋放任何鎖資源。其他線程都無法訪問被它佔用的鎖。直到對應的線程執行resume()方法後,被掛起的線程才能繼續,從而其他被阻塞在這個鎖的線程才能繼續執行。
如果resume操作出現在suspend操作之前,那麼線程就會一直處於阻塞狀態–>產生死鎖,但是對於被掛起的線程,他的線程仍舊是Runnable狀態
實例
class SuspendResumeTest {
public static Object object = new Object();
public static class TestThread extends Thread {
public TestThread(String name) {
super.setName(name);
}
@Override
public void run() {
synchronized (object) {
System.out.println(getName() + "佔用。。。");
Thread.currentThread().suspend();
}
}
}
static TestThread t1 = new TestThread("我是線程1");
static TestThread t2 = new TestThread("我是線程2");
public static void main(String[] args) throws InterruptedException {
t1.start();
//Thread.sleep(200);
t2.start();
t1.resume();
t2.resume();
t1.join();
t2.join();
}
}
執行結果:
產生死鎖!!!
接下來讓我們來說一下,常見的,推薦的wait和notify方法進行阻塞和喚醒吧~
在Object類中,定義了wait(),notify()和notifyAll()等接口。wait方法的作用是讓當前線程進入等待狀態,同時wait也會讓當前線程釋放它所持有的鎖。而notify()和notifyAll()的作用則是喚醒當前對象上的等待線程;notify()是喚醒單個線程,而notifyAll()是喚醒所有線程。
- notify:喚醒在此對象監視器上等待的單個線程
- notifyAll:喚醒在此對象監視器上等待的所有線程
- wait:讓當前線程處於“阻塞”狀態,直到其他線程調用該對象的notify方法或者notifyAll方法,當前線程被喚醒
- wait(long timeout):讓當前線程處於“阻塞”狀態,直到其他線程調用該對象的notify方法或者notifyAll方法,或者超過指定的時間,當前線程被喚醒
- wait(long timeout, int nanos):讓當前線程處於“阻塞”狀態,直到其他線程調用該對象的notify方法或者notifyAll方法,或者其他某個線程中斷當前線程,或者已經超過某個實際時間量,當前線程被喚醒
實例
public class WaitTest {
public static void main(String[] args) {
ThreadA t1=new ThreadA("t1");
synchronized (t1){
try {
//啓動線程1
System.out.println(Thread.currentThread().getName()+"start t1");
t1.start();
//主線程等待線程1通過notify喚醒
System.out.println(Thread.currentThread().getName()+"wait()");
t1.wait();
System.out.println(Thread.currentThread().getName()+"continue");
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
class ThreadA extends Thread{
public ThreadA(String name) {
super(name);
}
@Override
public void run() {
synchronized (this){
System.out.println(Thread.currentThread().getName()+"call notify()");
//喚醒當前的wait線程
notify();
}
}
}
######執行結果
結果說明
- 圖中的主線程代表main線程,線程t1代表waitTest中啓動的線程1,而鎖代表t1這個對象的同步鎖
- 主線程通過new ThreadA(“t1”)新建線程“t1”,隨後通過synchronized(t1)獲取t1對象的同步鎖,然後調用t1.start()啓動線程t1
- 主線程執行t1.wait()釋放"t1對象的鎖"並且進入阻塞狀態,等待t1對象上的線程通過notify或者notifyAll將其喚醒
- 線程t1運行之後,通過synchronized(this)獲取當前對象的鎖,接着調用notify喚醒當前對象上等待 的線程,也就是主線程
- 線程t1運行完畢之後,釋放當前對象的鎖,緊接着,主線程獲取t1對象的鎖,然後接着運行
####擴展一下wait()–>wait(long timeout)
class ThreadA extends Thread{
public ThreadA(String name) {
super(name);
}
public void run() {
System.out.println(Thread.currentThread().getName() + " run ");
// 死循環,不斷運行。
while(true)
;
}
}
public class WaitTimeoutTest {
public static void main(String[] args) {
ThreadA t1 = new ThreadA("t1");
synchronized(t1) {
try {
// 啓動“線程t1”
System.out.println(Thread.currentThread().getName() + " start t1");
t1.start();
// 主線程等待t1通過notify()喚醒 或 notifyAll()喚醒,或超過3000ms延時;然後才被喚醒。
System.out.println(Thread.currentThread().getName() + " call wait ");
t1.wait(3000);
System.out.println(Thread.currentThread().getName() + " continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
執行結果
main start t1
main call wait
t1 run // 大約3秒之後...輸出“main continue”
main continue
結果說明
和上面的wait方法類似,只不過主線程不會立刻被喚醒,而是等待3000ms後喚醒
擴展一下notify–>notifyAll
public class NotifyAllTest {
private static Object obj = new Object();
public static void main(String[] args) {
ThreadA t1 = new ThreadA("t1");
ThreadA t2 = new ThreadA("t2");
ThreadA t3 = new ThreadA("t3");
t1.start();
t2.start();
t3.start();
try {
System.out.println(Thread.currentThread().getName()+" sleep(3000)");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(obj) {
// 主線程等待喚醒。
System.out.println(Thread.currentThread().getName()+" notifyAll()");
obj.notifyAll();
}
}
static class ThreadA extends Thread{
public ThreadA(String name){
super(name);
}
public void run() {
synchronized (obj) {
try {
// 打印輸出結果
System.out.println(Thread.currentThread().getName() + " wait");
// 喚醒當前的wait線程
obj.wait();
// 打印輸出結果
System.out.println(Thread.currentThread().getName() + " continue");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
執行結果
t1 wait
main sleep(3000)
t3 wait
t2 wait
main notifyAll()
t2 continue
t3 continue
t1 continue
結果說明
- 主線程中新建並啓動了3個線程"t1" “t2” “t3”
- 主線程通過sleep(3000)休眠3秒,在主線程休眠3秒的過程中,我們假設"t1" “t2” “t3” 這3個線程都運行了,以"t1"爲例,當它運行的時候,它會執行obj.wait()等待其他線程通過notify或者notifyAll方法來喚醒她,同樣的道理,t2和t3也會等待其他線程通過notify或者notifyAll來喚醒它們
- 主線程休眠3秒後,接着運行,執行obj.notifyAll()喚醒obj上的等待線程,即喚醒"t1" “t2” "t3"這3個線程,緊接着,主線程的synchronized(obj)運行完畢後,主動釋放obj鎖,這樣t1 t2 t3就可以獲取鎖資源接着運行了。
notify() wait()等函數定義在object中,而不是Thread中的原因
Object中的wait(), notify()等函數,和synchronized一樣,會對“對象的同步鎖”進行操作。
wait()會使“當前線程”等待,因爲線程進入等待狀態,所以線程應該釋放它鎖持有的“同步鎖”,否則其它線程獲取不到該“同步鎖”而無法運行!
LockSupport提供的park和unpark方法
這兩個方法是基於suspend和resume這一組合的基礎上演變過來的,提供避免死鎖和競態條件
public class ThreadParkTest {
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.setName("mt");
mt.start();
try {
Thread.currentThread().sleep(10);
mt.park();
Thread.currentThread().sleep(30000);
mt.unPark();
Thread.currentThread().sleep(30000);
mt.park();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static class MyThread extends Thread {
private boolean isPark = false;
public void run() {
System.out.println(" Enter Thread running.....");
while (true) {
if (isPark) {
System.out.println("Thread is Park.....");
LockSupport.park();
}
}
}
public void park() {
isPark = true;
}
public void unPark() {
isPark = false;
LockSupport.unpark(this);
System.out.println("Thread is unpark.....");
}
}
}
爲什麼使用park和unpark方法可以避免死鎖的產生?
park和unpark方法控制的粒度更細,能夠準確的決定線程在某個點停止
引入了許可機制,邏輯爲:
1、park講許可在等於0的時候阻塞,等於1的時候返回並將許可減爲0
2、unpark嘗試喚醒線程,許可加1。根據這兩個邏輯,對於同一條線程,park與unpark先後操作的順序似乎並不影響程序正確地執行,假如先執行unpark操作,許可則爲1,之後再執行park操作,此時因爲許可等於1直接返回往下執行,並不執行阻塞操作。
總結總結總結
suspend()、resume()已經被deprecated,不建議使用。wait與notify要保證必須有鎖才能執行,而且執行notify操作釋放鎖後還要將當前線程扔進該對象鎖的等待隊列。LockSupport則完全不用考慮對象、鎖、等待隊列,真正解耦合線程之間的同步。