一、java線程的六種狀態
其中,RUNNABLE狀態包括 【運行中】 和 【就緒】;
BLOCKED(阻塞態)狀態只有在【等待進入synchronized方法(塊)】和 【其他Thread調用notify()或notifyAll(),但是還未獲得鎖】纔會進入;
二、sleep() 、yield()、join()與 wait()/notify()的區別
sleep() 、yield()、join()是Thread的方法,只放棄cpu,但是不放棄鎖
1、Thread.sleep(long millis),一定是當前線程調用此方法,當前線程進入TIMED_WAITING狀態,但不釋放對象鎖,millis後線程自動甦醒進入就緒狀態。作用:給其它線程執行機會的最佳方式。
2、Thread.yield(),一定是當前線程調用此方法,當前線程放棄獲取的CPU時間片,但不釋放鎖資源,由運行狀態變爲就緒狀態,讓OS再次選擇線程。作用:讓相同優先級的線程輪流執行,但並不保證一定會輪流執行。實際中無法保證yield()達到讓步目的,因爲讓步的線程還有可能被線程調度程序再次選中。Thread.yield()不會導致阻塞。該方法與sleep()類似,只是不能由用戶指定暫停多長時間。
3、t.join()/t.join(long millis),當前線程裏調用其它線程t的join方法,當前線程進入WAITING/TIMED_WAITING狀態,當前線程不會釋放已經持有的對象鎖。線程t執行完畢或者millis時間到,當前線程進入就緒狀態。
wait()是Object的方法,放棄cpu,也放棄鎖
4、obj.wait(),當前線程調用對象的wait()方法,當前線程釋放對象鎖,進入等待隊列。依靠notify()/notifyAll()喚醒或者wait(long timeout) timeout時間到自動喚醒。
5、obj.notify()喚醒在此對象監視器上等待的單個線程,選擇是任意性的。notifyAll()喚醒在此對象監視器上等待的所有線程。
public class Test {
public static void main(String[] args) {
new Thread1().start();
new Thread2().start();
}
public static class Thread1 extends Thread{
@Override
public void run() {
synchronized (Test.class){
System.out.println("Thread1 start");
try {
/**
* 1、wait()和notify()是Object鎖的方法
* 2、wait()會讓出鎖
*/
Test.class.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread1 go on ");
}
}
}
public static class Thread2 extends Thread{
@Override
public void run() {
synchronized (Test.class){
System.out.println("Thread2 start");
/**
* 1、notify()調用後,該線程會等待該同步塊執行完畢才釋放鎖
*/
Test.class.notifyAll();
try {
/**
* 1、sleep()是Thread的方法
* 2、sleep()不讓出鎖,只讓出cpu
*/
Thread2.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread2 go on");
}
}
}
}
三、LockSupport中的park() 和 unpark()
總結一下,LockSupport比Object的wait/notify有兩大優勢:
①LockSupport不需要在同步代碼塊裏 ,所以線程間也不需要維護一個共享的同步對象了,實現了線程間的解耦;
而wait/notify必須在同步塊或同步方法中才能調用。
②unpark函數可以先於park調用,所以不需要擔心線程間的執行的先後順序,而wait必須先於notify。
1、爲什麼LockSupport不需要在同步代碼塊裏而wait()需要?
線程A執行一段業務邏輯後調用wait阻塞住自己。主線程調用notify方法喚醒線程A,線程A然後打印自己執行的結果:
public class TestObjWait {
public static void main(String[] args)throws Exception {
final Object obj = new Object();
Thread A = new Thread(new Runnable() {
@Override
public void run() {
int sum = 0;
for(int i=0;i<10;i++){
sum+=i;
}
try {
synchronized (obj){
obj.wait();
}
}catch (Exception e){
e.printStackTrace();
}
System.out.println(sum);
}
});
A.start();
//睡眠一秒鐘,保證線程A已經計算完成,阻塞在wait方法
Thread.sleep(1000);
synchronized (obj){
obj.notify();
}
}
}
使用LockSupport實現:
public class TestObjWait {
public static void main(String[] args)throws Exception {
Thread A = new Thread(new Runnable() {
@Override
public void run() {
int sum = 0;
for(int i=0;i<10;i++){
sum+=i;
}
LockSupport.park();
System.out.println(sum);
}
});
A.start();
//睡眠一秒鐘,保證線程A已經計算完成
Thread.sleep(1000);
LockSupport.unpark(A);
}
}
2、爲什麼LockSupport不需要擔心unpark函數和park調用順序,而Object的wait/notify需要關心?
如果我們將上面代碼的這一句去掉:
//睡眠一秒鐘,保證線程A已經計算完成
Thread.sleep(1000);
那麼,使用wait()和notify()的就會出題,可能A會永遠被掛起,因爲主線程的notify()先於wait()調用了;
但是LockSupport的代碼還是正確的執行,因爲
LockSupport和每個使用它的線程都與一個許可(permit)關聯。permit相當於1,0的開關,默認是0;
調用unpark就將permit賦值1;
調用park時,會判斷permit如果爲1,就會將permit賦值0,並且立即返回,如果permit爲0,會阻塞在這裏,直到permit變爲1