一.
至于同步函數用的是哪個鎖,我們可以驗證一下,借助原先賣票的例子
?對于程序中的num,從100改為400,DOS的結果顯示的始終都是0線程,票號最小都是1。
票號是沒有問題的,因為同步了。
有人針對只出現0線程,說是票數太少,0線程都給操作完了。即使改成四萬張票,也是0線程操作。
正常來說,四個線程0~3,誰搶到誰就運行。問題出現在哪兒?
程序中run函數是public synchronized void run(),沒有搞清楚什么時候用同步,什么時候不用同步。
我們知道該同步的是if整個語句,
現在我們是從run方法就開始了同步,也就是說在run語句的上面,同時來了四個線程,一個線程在run方法里沒執行完所有的語句,其他線程就無法進來。而run里面就包含著循環售票,也就是說一個線程進到run方法里,售完了票才出去。
看下圖,在if中0線程處于sleep狀態時,1~3號線程得到了執行權,但是進不來。
而且,0線程出不去了,因為while中的判斷始終是正確的。(是不是說,即使循環執行完了,0線程還呆在里面,因為while始終是正確的?)
出現這樣的原因就是不需要將run方法整體同步,只需要將if代碼塊同步,怎么解決呢?
單獨地將if語句定義成函數,將該函數同步,接著調用即可。
DOS結果顯示,四個線程都出來了。(這里show方法可以直接調用,不用創建對象啥的么?)
二. 驗證同步函數的鎖
基于上面的例子,我們來驗證同步函數的鎖是哪一個?
現在為了方便起見,將四個線程減少為兩個線程。兩個線程運行的動作是一樣的,都在賣票。一個是在同步代碼塊里賣票,一個是在同步函數中賣票。(同步函數和同步代碼塊兩者不是一樣么?)
如果這兩個線程用的是同一個鎖的話,就不會出現安全隱患。0線程在同步函數里,1線程在同步代碼塊里,如果它倆用的是同一個鎖,那說明0線程在運行同步函數的時候,1線程不能運行同步代碼塊的,(這是不是說明同步代碼塊和同步函數都是靠鎖來操作的原理?)
想要在run方法里,既有同步代碼塊又能調用同步函數,這需要什么動作?這叫做線程的切換。為真的時候,運行同步代碼塊:為假的時候,運行同步函數,這需要一個boolean型變量。
(這里的while語句始終感覺沒什么用,為什么要一直保留著?)
?為真的時候,走同步代碼塊,為假的時候,走同步代碼塊。
兩個線程都進到run方法里面去了,它們都有自己的run方法,判斷的變量也是同一變量flag。
DOS結果線程,1線程和0線程都有,但是都在function里面執行。按理說,0線程為真應該在同步代碼塊中執行,怎么跑到同步函數中執行了?理由:主線程開啟以后,創建對象,創建兩個線程。開啟線程1以后,它還持有cpu的執行權,所以瞬間,將t1.start(),t.flag=false,t2.start()這三句話全部都搞定了。一搞定后,這個flag就變為假了,主線程搞完假后,這兩個線程在啟動的時候都是flag=false,因此兩個線程在執行的時候,執行的都是同步函數。(為什么主線程能執行這么多語句?怎么判別主線程和0,1線程是執行的哪些語句?)
有人說這里沒切換啊。可以做切換。
主線程開啟了0線程以后,把它置為假之前,可以讓主線程停一下。也就是調用sleep方法,讓主線程睡一下,這樣0線程就掌握執行權了。
睡了10毫秒。
DOS結果顯示如上。如果兩個線程同步了,就不會出現負的情況,如果沒同步就有可能出現安全問題。
怎么輸出兩個49?操作線程的代碼有四句,obj兩句,function兩句。你判斷完了,我也判斷完了,你沒輸出,我也沒輸出。我49輸出,我也49輸出。
但是,我現在想說的是0號票,打印0號票肯定是不對的。加上同步的居然不安全。為什么?
首先這里面應該有多線程,同步代碼塊里面是一個線程,同步函數里用的又是另一個線程,它們用的 不是一個鎖。如果用的是用一個鎖,代表著同一個鎖里有多個線程,意味著每次只能有一個線程進來。這里可以說明的一點的是,同步函數用的鎖肯定不是obj,那用的是什么鎖?同步函數僅僅是函數上帶了同步性,同步本身不帶鎖。同步代碼塊后面是單獨指定鎖,synchronized是關鍵字,本身并不帶鎖。
應該是函數帶的鎖,函數有對象,函數被調用的時候,必須是對象來調用。函數是被哪個對象調用呢?
函數是被this調用,函數都有自己所屬的this。函數被哪個對象調用?我哪兒知道,我肯定函數是被對象調用,憑什么去操作對應的數據啊?因為持有this。
這個show被誰調用?被run方法調用,至于run方法被誰調用,換句話說run方法所屬于哪個對象。當然屬于t。run方法不是封裝線程任務么?不是把線程任務所屬對象t創建出來了么,那么就是t在調用run方法對象。show怎么獲取的t,當然this嘛。
一般方法調用一般方法,直接寫個this,即this,show();
run方法也屬于this,直接把this寫入同步代碼塊中,哪個對象調用這個run方法,它就代表哪個對象。這個this所指的地址和下面的t地址是一致的
DOS結果顯示,兩者是同一個對象。
現在將添加的兩個輸出語句注釋掉,
繼續編譯運行
0線程和1線程將票賣完了,也不存在0號票。由此可以驗證同步函數使用的鎖是this。
上面的程序可以不用寫那么多,現在為什么寫呢?是為了講解同步函數使用的鎖是this。
同步函數可以是同步代碼塊的簡寫,一簡寫就有前提,有弊端。如果同步代碼塊里的鎖不是this,那就不能用同步函數了。
同步函數雖然簡化,但是鎖是唯一的。
?
?
?
?
?
?
?
?
?
?
?
?
?