java代碼:
package myTest;public class TestThread extends Thread{ private Integer ticketNum = 10;
public void run() {
synchronized (this) {//這個同步代碼塊和下面的while調(diào)換位置會有完全不同的結(jié)果
while(true) {
if(this.ticketNum > 0) {
try {
System.out.println(Thread.currentThread().getName()+":"+this.ticketNum);
this.ticketNum --;
Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } }else{ break; } } } } public static void main(String[] args) { TestThread t = new TestThread();//這么寫才能保證鎖住的this是同一個實例對象
new Thread(t, "t1").start();
new Thread(t, "t2").start();
/* 下面這種寫法,每個線程都是不同的一個新的ThreadTest的實例對象,鎖住的this分別是不同的對象,所以互不阻塞 ThreadTest t = new ThreadTest(); t.setName("t1"); t.start(); ThreadTest t2 = new ThreadTest(); t2.setName("t2"); t2.start(); ThreadTest t3 = new ThreadTest(); t3.setName("t3"); t3.start(); */
ThreadTest t = new ThreadTest(); t.setName("t1"); t.start(); ThreadTest t2 = new ThreadTest(); t2.setName("t2"); t2.start(); ThreadTest t3 = new ThreadTest(); t3.setName("t3"); t3.start(); */ }}
解析:
(1)寫在while外面的時候:t1,t2任意一個線程先拿到執(zhí)行權(quán)的話就會一直是它在循環(huán)了。因為沒有能停下來的語句,別的線程也執(zhí)行不了,因為synchronized(){}是鎖的。只有該線程循環(huán)完出來的時候,synchronized(){}才是打開的。這時候就不能滿足t1,t2隨機進入while循環(huán);此時synchronized(){}鎖住的是整個代碼塊,而同步代碼中的sleep()是不會釋放鎖的,會繼續(xù)執(zhí)行sleep()后面的代碼,繼續(xù)執(zhí)行所以t1和t2誰拿到鎖就會一直執(zhí)行,直到while跳出(如果改為this.wait(),則會釋放鎖,此時t1,t2誰拿到鎖,誰就會進入同步代碼塊)
(2)如果寫在while里面就不同了,這二個線程不論是誰得到cpu執(zhí)行權(quán)都會先進入while循環(huán),然后判斷synchronized(){}是否是鎖的,如果不是就可以賣出一張票,如果是就等待別的線程先執(zhí)行完。例如t2拿到鎖,正在執(zhí)行同步代碼塊,此時t1進入while里面,因為t2拿到鎖,所以t1要等待t2執(zhí)行完,當(dāng)t2執(zhí)行完同步代碼塊之后,會再次進入while的下一個循環(huán),此時和t1和t2會競爭鎖,拿到鎖的會進入同步代碼塊,此時不會出現(xiàn)只有一個窗口賣票的情況,而是都在賣票...
(3)簡單拿模擬窗口賣票來說,我們看到的執(zhí)行效果的區(qū)別就是:
A:鎖在while外面的時候,運行程序,不管有多少個窗口也總是一個窗口在賣票,從頭賣到尾絕對永遠(yuǎn)都是先進入循環(huán)的那個線程會把所有票賣完
B:鎖在while循環(huán)里面的時候,會是隨機的一個窗口在賣,如果是二個線程的話,就應(yīng)該是二個窗口都有在賣。
java多線程什么時候釋放鎖—wait()、notify()
https://www.cnblogs.com/hy928302776/p/3255641.html
由于等待一個鎖定線程只有在獲得這把鎖之后,才能恢復(fù)運行,所以讓持有鎖的線程在不需要鎖的時候及時釋放鎖是很重要的。
在以下情況下,持有鎖的線程會釋放鎖:
1. 執(zhí)行完同步代碼塊。
2. 在執(zhí)行同步代碼塊的過程中,遇到異常而導(dǎo)致線程終止。
3. 在執(zhí)行同步代碼塊的過程中,執(zhí)行了鎖所屬對象的wait()方法,這個線程會釋放鎖,進行對象的等待池。
除了以上情況外,只要持有鎖的此案吃還沒有執(zhí)行完同步代碼塊,就不會釋放鎖。
因此在以下情況下,線程不會釋放鎖:
1. 在執(zhí)行同步代碼塊的過程中,執(zhí)行了Thread.sleep()方法,當(dāng)前線程放棄CPU,開始睡眠,在睡眠中不會釋放鎖。該方法調(diào)用時,可以看到線程明顯有被阻塞。但是設(shè)定一過,就自己開始繼續(xù)執(zhí)行。可以說明sleep方法會讓線程阻塞,但是并沒有放棄自己所持有的鎖,阻塞完畢后,它任然持有鎖,可以接著執(zhí)行之前阻塞之后的任務(wù)。
2. 在執(zhí)行同步代碼塊的過程中,執(zhí)行了Thread.yield()方法,當(dāng)前線程放棄CPU,但不會釋放鎖。
3. 在執(zhí)行同步代碼塊的過程中,其他線程執(zhí)行了當(dāng)前對象的suspend()方法,當(dāng)前線程被暫停,但不會釋放鎖。但Thread類的suspend()方法已經(jīng)被廢棄。
避免死鎖的一個通用的經(jīng)驗法則是:當(dāng)幾個線程都要訪問共享資源A、B和C時,保證使每個線程都按照同樣的順序去訪問他們,比如都先訪問A,再訪問B和C。
java.lang.Object類中提供了兩個用于線程通信的方法:wait()和notify()。需要注意到是,wait()方法必須放在一個循環(huán)中,因為在多線程環(huán)境中,共享對象的狀態(tài)隨時可能改變。當(dāng)一個在對象等待池中的線程被喚醒后,并不一定立即恢復(fù)運行,等到這個線程獲得了鎖及CPU才能繼續(xù)運行,又可能此時對象的狀態(tài)已經(jīng)發(fā)生了變化。
# 調(diào)用obj的wait(), notify()方法前,必須獲得obj鎖,也就是必須寫在synchronized(obj) {...} 代碼段內(nèi)。
# 調(diào)用obj.wait()后,線程A就釋放了obj的鎖,否則線程B無法獲得obj鎖,也就無法在synchronized(obj) {...} 代碼段內(nèi)喚醒A。
# 當(dāng)obj.wait()方法返回后,線程A需要再次獲得obj鎖,才能繼續(xù)執(zhí)行。
# 如果A1,A2,A3都在obj.wait(),則B調(diào)用obj.notify()只能喚醒A1,A2,A3中的一個(具體哪一個由JVM決定)。
# obj.notifyAll()則能全部喚醒A1,A2,A3,但是要繼續(xù)執(zhí)行obj.wait()的下一條語句,必須獲得obj鎖,
因此,A1,A2,A3只有一個有機會獲得鎖繼續(xù)執(zhí)行,例如A1,其余的需要等待A1釋放obj鎖之后才能繼續(xù)執(zhí)行。
# 當(dāng)B調(diào)用obj.notify/notifyAll的時候,B正持有obj鎖,因此,A1,A2,A3雖被喚醒,但是仍無法獲得obj鎖。直到B退出synchronized塊,釋放obj鎖后,A1,A2,A3中的一個才有機會獲得鎖繼續(xù)執(zhí)行。
wait()/sleep()的區(qū)別
前面講了wait/notify機制,Thread還有一個sleep()靜態(tài)方法,它也能使線程暫停一段時間。sleep與wait的不同點是:sleep并不釋放鎖,并且sleep的暫停和wait暫停是不一樣的。obj.wait會使線程進入obj對象的等待集合中并等待喚醒。
但是wait()和sleep()都可以通過interrupt()方法打斷線程的暫停狀態(tài),從而使線程立刻拋出InterruptedException。
如果線程A希望立即結(jié)束線程B,則可以對線程B對應(yīng)的Thread實例調(diào)用interrupt方法。如果此刻線程B正在wait/sleep/join,則線程B會立刻拋出InterruptedException,在catch() {} 中直接return即可安全地結(jié)束線程。
需要注意的是,InterruptedException是線程自己從內(nèi)部拋出的,并不是interrupt()方法拋出的。對某一線程調(diào)用interrupt()時,如果該線程正在執(zhí)行普通的代碼,那么該線程根本就不會拋出InterruptedException。但是,一旦該線程進入到wait()/sleep()/join()后,就會立刻拋出InterruptedException。