)7、樂觀鎖 相信,這里是安全的。概念:這里拋開數據庫來談樂觀鎖和悲觀鎖,扯上數據庫總會覺得和java離得很遠.悲觀鎖:一段執行邏輯加上悲觀鎖,不同線程同時執行時,只能有一個線程執行,其他的線程在入口處等待,直到鎖被釋放.樂觀鎖:一段執行邏輯加上樂觀鎖,不同線程同時執行時,可以同時進入執行,在最后更新數據的時候要檢查這些數據是否被其他線程修改了(版本和執行初是否相同),沒有修改則進行更新,否則放棄本次操作.從解釋上可以看出,悲觀鎖具有很強的獨占性,也是最安全的.而樂觀鎖很開放,效率高,安全性比悲觀鎖低,因為在樂觀鎖檢查數據版本一致性時也可能被其他線程修改數據.從下面的例子中可以看出來這里說的安全差別.樂觀鎖例子:復制代碼復制代碼package note.com;/*** 樂觀鎖* * 場景:有一個對象value,需要被兩個線程調用,由于是共享數據,存在臟數據的問題* 悲觀鎖可以利用synchronized實現,這里不提.* 現在用樂觀鎖來解決這個臟數據問題* * @author lxz**/public class OptimisticLock {public static int value = 0; // 多線程同時調用的操作對象/*** A線程要執行的方法*/public static void invoke(int Avalue, String i)throws InterruptedException {Thread.sleep(1000L);//延長執行時間if (Avalue != value) {//判斷value版本System.out.PRintln(Avalue + ":" + value + "A版本不一致,不執行");value--;} else {Avalue++;//對數據操作value = Avalue;;//對數據操作System.out.println(i + ":" + value);}}/*** B線程要執行的方法*/public static void invoke2(int Bvalue, String i)throws InterruptedException {Thread.sleep(1000L);//延長執行時間if (Bvalue != value) {//判斷value版本System.out.println(Bvalue + ":" + value + "B版本不一致,不執行");} else {System.out.println("B:利用value運算,value="+Bvalue);}}/*** 測試,期待結果:B線程執行的時候value數據總是當前最新的*/public static void main(String[] args) throws InterruptedException {new Thread(new Runnable() {//A線程public void run() {try {for (int i = 0; i < 3; i++) {int Avalue = OptimisticLock.value;//A獲取的valueOptimisticLock.invoke(Avalue, "A");}} catch (InterruptedException e) {e.printStackTrace();}}}).start();new Thread(new Runnable() {//B線程public void run() {try {for (int i = 0; i < 3; i++) {int Bvalue = OptimisticLock.value;//B獲取的valueOptimisticLock.invoke2(Bvalue, "B");}} catch (InterruptedException e) {e.printStackTrace();}}}).start();}}復制代碼復制代碼測試結果:A:10:1B版本不一致,不執行B:利用value運算,value=1A:2B:利用value運算,value=2A:3從結果中看出,B線程在執行的時候最后發現自己的value和執行前不一致,說明被A修改了,那么放棄了本次執行.多運行幾次發現了下面的結果:A:1B:利用value運算,value=0A:21:2B版本不一致,不執行A:3B:利用value運算,value=2從結果看A修改了value值,B卻沒有檢查出來,利用錯誤的value值進行了操作. 為什么會這樣呢?這里就回到前面說的樂觀鎖是有一定的不安全性的,B在檢查版本的時候A還沒有修改,在B檢查完版本后更新數據前(例子中的輸出語句),A更改了value值,這時B執行更新數據(例子中的輸出語句)就發生了與現存value不一致的現象.針對這個問題,我覺得樂觀鎖要解決這個問題還需要在檢查版本與更新數據這個操作的時候能夠使用悲觀鎖,比如加上synchronized,讓它在最后一步保證數據的一致性.這樣既保證多線程都能同時執行,犧牲最后一點的性能去保證數據的一致.補充感謝評論中提出的cas方式解決樂觀鎖最后的安全問題,以前不知道cas(比較-交換)這個在java中的存在,找了找資料才發現java的concurrent包確實使用的cas實現樂觀鎖的數據同步問題.下面是我對這兩種方式的一點看法:有兩種方式來保證樂觀鎖最后同步數據保證它原子性的方法1,CAS方式:Java非公開API類Unsafe實現的CAS(比較-交換),由C++編寫的調用硬件操作內存,保證這個操作的原子性,concurrent包下很多樂觀鎖實現使用到這個類,但這個類不作為公開API使用,隨時可能會被更改.我在本地測試了一下,確實不能夠直接調用,源碼中Unsafe是私有構造函數,只能通過getUnsafe方法獲取單例,首先去掉eclipse的檢查(非API的調用限制)限制以后,執行發現報 java.lang.SecurityException異常,源碼中getUnsafe方法中執行訪問檢查,看來java不允許應用程序獲取Unsafe類. 值得一提的是反射是可以得到這個類對象的.2,加鎖方式:利用Java提供的現有API來實現最后數據同步的原子性(用悲觀鎖).看似樂觀鎖最后還是用了悲觀鎖來保證安全,效率沒有提高.實際上針對于大多數只執行不同步數據的情況,效率比悲觀加鎖整個方法要高.特別注意:針對一個對象的數據同步,悲觀鎖對這個對象加鎖和樂觀鎖效率差不多,如果是多個需要同步數據的對象,樂觀鎖就比較方便.8、公平鎖 有優先級的鎖()
7、樂觀鎖 相信,這里是安全的。
概念:
這里拋開數據庫來談樂觀鎖和悲觀鎖,扯上數據庫總會覺得和Java離得很遠.
悲觀鎖:一段執行邏輯加上悲觀鎖,不同線程同時執行時,只能有一個線程執行,其他的線程在入口處等待,直到鎖被釋放.
樂觀鎖:一段執行邏輯加上樂觀鎖,不同線程同時執行時,可以同時進入執行,在最后更新數據的時候要檢查這些數據是否被其他線程修改了(版本和執行初是否相同),沒有修改則進行更新,否則放棄本次操作.
從解釋上可以看出,悲觀鎖具有很強的獨占性,也是最安全的.而樂觀鎖很開放,效率高,安全性比悲觀鎖低,因為在樂觀鎖檢查數據版本一致性時也可能被其他線程修改數據.從下面的例子中可以看出來這里說的安全差別.
樂觀鎖例子:
復制代碼
復制代碼
package note.com;
/**
* 樂觀鎖
*
* 場景:有一個對象value,需要被兩個線程調用,由于是共享數據,存在臟數據的問題
* 悲觀鎖可以利用synchronized實現,這里不提.
* 現在用樂觀鎖來解決這個臟數據問題
*
* @author lxz
*
*/
public class OptimisticLock {
public static int value = 0; // 多線程同時調用的操作對象
/**
* A線程要執行的方法
*/
public static void invoke(int Avalue, String i)
throws InterruptedException {
Thread.sleep(1000L);//延長執行時間
if (Avalue != value) {//判斷value版本
System.out.println(Avalue + ":" + value + "A版本不一致,不執行");
value--;
} else {
Avalue++;//對數據操作
value = Avalue;;//對數據操作
System.out.println(i + ":" + value);
}
}
/**
* B線程要執行的方法
*/
public static void invoke2(int Bvalue, String i)
throws InterruptedException {
Thread.sleep(1000L);//延長執行時間
if (Bvalue != value) {//判斷value版本
System.out.println(Bvalue + ":" + value + "B版本不一致,不執行");
} else {
System.out.println("B:利用value運算,value="+Bvalue);
}
}
/**
* 測試,期待結果:B線程執行的時候value數據總是當前最新的
*/
public static void main(String[] args) throws InterruptedException {
new Thread(new Runnable() {//A線程
public void run() {
try {
for (int i = 0; i < 3; i++) {
int Avalue = OptimisticLock.value;//A獲取的value
OptimisticLock.invoke(Avalue, "A");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {//B線程
public void run() {
try {
for (int i = 0; i < 3; i++) {
int Bvalue = OptimisticLock.value;//B獲取的value
OptimisticLock.invoke2(Bvalue, "B");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
復制代碼
復制代碼
測試結果:
A:1
0:1B版本不一致,不執行
B:利用value運算,value=1
A:2
B:利用value運算,value=2
A:3
從結果中看出,B線程在執行的時候最后發現自己的value和執行前不一致,說明被A修改了,那么放棄了本次執行.
多運行幾次發現了下面的結果:
A:1
B:利用value運算,value=0
A:2
1:2B版本不一致,不執行
A:3
B:利用value運算,value=2
從結果看A修改了value值,B卻沒有檢查出來,利用錯誤的value值進行了操作. 為什么會這樣呢?
這里就回到前面說的樂觀鎖是有一定的不安全性的,B在檢查版本的時候A還沒有修改,在B檢查完版本后更新數據前(例子中的輸出語句),A更改了value值,這時B執行更新數據(例子中的輸出語句)就發生了與現存value不一致的現象.
針對這個問題,我覺得樂觀鎖要解決這個問題還需要在檢查版本與更新數據這個操作的時候能夠使用悲觀鎖,比如加上synchronized,讓它在最后一步保證數據的一致性.這樣既保證多線程都能同時執行,犧牲最后一點的性能去保證數據的一致.
補充
感謝評論中提出的cas方式解決樂觀鎖最后的安全問題,以前不知道cas(比較-交換)這個在java中的存在,找了找資料才發現java的concurrent包確實使用的cas實現樂觀鎖的數據同步問題.
下面是我對這兩種方式的一點看法:
有兩種方式來保證樂觀鎖最后同步數據保證它原子性的方法
1,CAS方式:Java非公開API類Unsafe實現的CAS(比較-交換),由C++編寫的調用硬件操作內存,保證這個操作的原子性,concurrent包下很多樂觀鎖實現使用到這個類,但這個類不作為公開API使用,隨時可能會被更改.我在本地測試了一下,確實不能夠直接調用,源碼中Unsafe是私有構造函數,只能通過getUnsafe方法獲取單例,首先去掉eclipse的檢查(非API的調用限制)限制以后,執行發現報 java.lang.SecurityException異常,源碼中getUnsafe方法中執行訪問檢查,看來java不允許應用程序獲取Unsafe類. 值得一提的是反射是可以得到這個類對象的.
2,加鎖方式:利用Java提供的現有API來實現最后數據同步的原子性(用悲觀鎖).看似樂觀鎖最后還是用了悲觀鎖來保證安全,效率沒有提高.實際上針對于大多數只執行不同步數據的情況,效率比悲觀加鎖整個方法要高.特別注意:針對一個對象的數據同步,悲觀鎖對這個對象加鎖和樂觀鎖效率差不多,如果是多個需要同步數據的對象,樂觀鎖就比較方便.
8、公平鎖 有優先級的鎖(轉載http://ifeve.com/starvation-and-fairness/#more-4370)
9、非公平鎖 無優先級的鎖
如果一個線程因為CPU時間全部被其他線程搶走而得不到CPU運行時間,這種狀態被稱之為“饑餓”。而該線程被“饑餓致死”正是因為它得不到CPU運行時間的機會。解決饑餓的方案被稱之為“公平性” – 即所有線程均能公平地獲得運行機會。
下面是本文討論的主題:
1. Java中導致饑餓的原因:
高優先級線程吞噬所有的低優先級線程的CPU時間。
線程被永久堵塞在一個等待進入同步塊的狀態。
線程在等待一個本身也處于永久等待完成的對象(比如調用這個對象的wait方法)。
2. 在Java中實現公平性方案,需要:
使用鎖,而不是同步塊。
公平鎖。
注意性能方面。
Java中導致饑餓的原因
在Java中,下面三個常見的原因會導致線程饑餓:
高優先級線程吞噬所有的低優先級線程的CPU時間。
線程被永久堵塞在一個等待進入同步塊的狀態,因為其他線程總是能在它之前持續地對該同步塊進行訪問。
線程在等待一個本身(在其上調用wait())也處于永久等待完成的對象,因為其他線程總是被持續地獲得喚醒。
高優先級線程吞噬所有的低優先級線程的CPU時間
你能為每個線程設置獨自的線程優先級,優先級越高的線程獲得的CPU時間越多,線程優先級值設置在1到10之間,而這些優先級值所表示行為的準確解釋則依賴于你的應用運行平臺。對大多數應用來說,你最好是不要改變其優先級值。
線程被永久堵塞在一個等待進入同步塊的狀態
Java的同步代碼區也是一個導致饑餓的因素。Java的同步代碼區對哪個線程允許進入的次序沒有任何保障。這就意味著理論上存在一個試圖進入該同步區的線程處于被永久堵塞的風險,因為其他線程總是能持續地先于它獲得訪問,這即是“饑餓”問題,而一個線程被“饑餓致死”正是因為它得不到CPU運行時間的機會。
線程在等待一個本身(在其上調用wait())也處于永久等待完成的對象
如果多個線程處在wait()方法執行上,而對其調用notify()不會保證哪一個線程會獲得喚醒,任何線程都有可能處于繼續等待的狀態。因此存在這樣一個風險:一個等待線程從來得不到喚醒,因為其他等待線程總是能被獲得喚醒。
在Java中實現公平性
雖Java不可能實現100%的公平性,我們依然可以通過同步結構在線程間實現公平性的提高。
首先來學習一段簡單的同步態代碼:
1
public class Synchronizer{
2
3
public synchronized void doSynchronized(){
4
5
//do a lot of work which takes a long time
6
7
}
8
}
如果有一個以上的線程調用doSynchronized()方法,在第一個獲得訪問的線程未完成前,其他線程將一直處于阻塞狀態,而且在這種多線程被阻塞的場景下,接下來將是哪個線程獲得訪問是沒有保障的。
使用鎖方式替代同步塊
為了提高等待線程的公平性,我們使用鎖方式來替代同步塊。
1
public class Synchronizer{
2
Lock lock = new Lock();
3
public void doSynchronized() throws InterruptedException{
4
this.lock.lock();
5
//critical section, do a lot of work which takes a long time
6
this.lock.unlock();
7
}
8
}
注意到doSynchronized()不再聲明為synchronized,而是用lock.lock()和lock.unlock()來替代。
下面是用Lock類做的一個實現:
01
public class Lock{
02
03
private boolean isLocked = false;
04
05
private Thread lockingThread = null;
06
07
public synchronized void lock() throws InterruptedException{
08
09
while(isLocked){
10
11
wait();
12
13
}
14
15
isLocked = true;
16
17
lockingThread = Thread.currentThread();
18
19
}
20
21
public synchronized void unlock(){
22
23
if(this.lockingThread != Thread.currentThread()){
24
25
throw new IllegalMonitorStateException(
26
27
"Calling thread has not locked this lock");
28
29
}
30
31
isLocked = false;
32
33
lockingThread = null;
34
35
notify();
36
37
}
38
}
注意到上面對Lock的實現,如果存在多線程并發訪問lock(),這些線程將阻塞在對lock()方法的訪問上。另外,如果鎖已經鎖上(校對注:這里指的是isLocked等于true時),這些線程將阻塞在while(isLocked)循環的wait()調用里面。要記住的是,當線程正在等待進入lock() 時,可以調用wait()釋放其鎖實例對應的同步鎖,使得其他多個線程可以進入lock()方法,并調用wait()方法。
這回看下doSynchronized(),你會注意到在lock()和unlock()之間的注釋:在這兩個調用之間的代碼將運行很長一段時間。進一步設想,這段代碼將長時間運行,和進入lock()并調用wait()來比較的話。這意味著大部分時間用在等待進入鎖和進入臨界區的過程是用在wait()的等待中,而不是被阻塞在試圖進入lock()方法中。
在早些時候提到過,同步塊不會對等待進入的多個線程誰能獲得訪問做任何保障,同樣當調用notify()時,wait()也不會做保障一定能喚醒線程(至于為什么,請看線程通信)。因此這個版本的Lock類和doSynchronized()那個版本就保障公平性而言,沒有任何區別。
但我們能改變這種情況。當前的Lock類版本調用自己的wait()方法,如果每個線程在不同的對象上調用wait(),那么只有一個線程會在該對象上調用wait(),Lock類可以決定哪個對象能對其調用notify(),因此能做到有效的選擇喚醒哪個線程。
公平鎖
下面來講述將上面Lock類轉變為公平鎖FairLock。你會注意到新的實現和之前的Lock類中的同步和wait()/notify()稍有不同。
準確地說如何從之前的Lock類做到公平鎖的設計是一個漸進設計的過程,每一步都是在解決上一步的問題而前進的:Nested Monitor Lockout, Slipped Conditions和Missed Signals。這些本身的討論雖已超出本文的范圍,但其中每一步的內容都將會專題進行討論。重要的是,每一個調用lock()的線程都會進入一個隊列,當解鎖后,只有隊列里的第一個線程被允許鎖住Farlock實例,所有其它的線程都將處于等待狀態,直到他們處于隊列頭部。
01
public class FairLock {
02
private boolean isLocked = false;
03
private Thread lockingThread = null;
04
private List<QueueObject> waitingThreads =
05
new ArrayList<QueueObject>();
06
07
public void lock() throws InterruptedException{
08
QueueObject queueObject = new QueueObject();
09
boolean isLockedForThisThread = true;
10
synchronized(this){
11
waitingThreads.add(queueObject);
12
}
13
14
while(isLockedForThisThread){
15
synchronized(this){
16
isLockedForThisThread =
17
isLocked || waitingThreads.get(0) != queueObject;
18
if(!isLockedForThisThread){
19
isLocked = true;
20
waitingThreads.remove(queueObject);
21
lockingThread = Thread.currentThread();
22
return;
23
}
24
}
25
try{
26
queueObject.doWait();
27
}catch(InterruptedException e){
28
synchronized(this) { waitingThreads.remove(queueObject); }
29
throw e;
30
}
31
}
32
}
33
34
public synchronized void unlock(){
35
if(this.lockingThread != Thread.currentThread()){
36
throw new IllegalMonitorStateException(
37
"Calling thread has not locked this lock");
38
}
39
isLocked = false;
40
lockingThread = null;
41
if(waitingThreads.size() > 0){
42
waitingThreads.get(0).doNotify();
43
}
44
}
45
}
01
public class QueueObject {
02
03
private boolean isNotified = false;
04
05
public synchronized void doWait() throws InterruptedException {
06
07
while(!isNotified){
08
this.wait();
09
}
10
11
this.isNotified = false;
12
13
}
14
15
public synchronized void doNotify() {
16
this.isNotified = true;
17
this.notify();
18
}
19
20
public boolean equals(Object o) {
21
return this == o;
22
}
23
24
}
首先注意到lock()方法不在聲明為synchronized,取而代之的是對必需同步的代碼,在synchronized中進行嵌套。
FairLock新創建了一個QueueObject的實例,并對每個調用lock()的線程進行入隊列。調用unlock()的線程將從隊列頭部獲取QueueObject,并對其調用doNotify(),以喚醒在該對象上等待的線程。通過這種方式,在同一時間僅有一個等待線程獲得喚醒,而不是所有的等待線程。這也是實現FairLock公平性的核心所在。
請注意,在同一個同步塊中,鎖狀態依然被檢查和設置,以避免出現滑漏條件。
還需注意到,QueueObject實際是一個semaphore。doWait()和doNotify()方法在QueueObject中保存著信號。這樣做以避免一個線程在調用queueObject.doWait()之前被另一個調用unlock()并隨之調用queueObject.doNotify()的線程重入,從而導致信號丟失。queueObject.doWait()調用放置在synchronized(this)塊之外,以避免被monitor嵌套鎖死,所以另外的線程可以解鎖,只要當沒有線程在lock方法的synchronized(this)塊中執行即可。
最后,注意到queueObject.doWait()在try – catch塊中是怎樣調用的。在InterruptedException拋出的情況下,線程得以離開lock(),并需讓它從隊列中移除。
性能考慮
如果比較Lock和FairLock類,你會注意到在FairLock類中lock()和unlock()還有更多需要深入的地方。這些額外的代碼會導致FairLock的同步機制實現比Lock要稍微慢些。究竟存在多少影響,還依賴于應用在FairLock臨界區執行的時長。執行時長越大,FairLock帶來的負擔影響就越小,當然這也和代碼執行的頻繁度相關。
10、偏向鎖 無競爭不鎖,有競爭掛起,轉為輕量鎖(轉載)
14、輕量級鎖 CAS 實現
11、對象鎖 鎖住對象(轉載http://ifeve.com/?x=42&y=9&s=%E5%AF%B9%E8%B1%A1%E9%94%81)
類鎖和對象鎖是否會沖突?對象鎖和私有鎖是否會沖突?通過實例來進行說明。
一、相關約定
為了明確后文的描述,先對本文涉及到的鎖的相關定義作如下約定:
1. 類鎖:在代碼中的方法上加了static和synchronized的鎖,或者synchronized(xxx.class)的代碼段,如下文中的increament();
2.對象鎖:在代碼中的方法上加了synchronized的鎖,或者synchronized(this)的代碼段,如下文中的synOnMethod()和synInMethod();
3.私有鎖:在類內部聲明一個私有屬性如private Object lock,在需要加鎖的代碼段synchronized(lock),如下文中的synMethodWithObj()。
二、測試代碼
1.編寫一個啟動類ObjectLock
public class ObjectLock {
public static void main(String[] args) {
System.out.println("start time = " + System.currentTimeMillis()+"ms");
LockTestClass test = new LockTestClass();
for (int i = 0; i < 3; i++) {
Thread thread = new ObjThread(test, i);
thread.start();
}
}
}
2.編寫一個線程類ObjThread,用于啟動同步方法(注意它的run方法可能會調整以進行不同的測試)
public class ObjThread extends Thread {
LockTestClass lock;
int i = 0;
public ObjThread(LockTestClass lock, int i) {
this.lock = lock;
this.i = i;
}
public void run() {
//無鎖方法
//lock.noSynMethod(this.getId(),this);
//對象鎖方法1,采用synchronized synInMethod的方式
lock.synInMethod();
//對象鎖方法2,采用synchronized(this)的方式
//lock.synOnMethod();
//私有鎖方法,采用synchronized(object)的方式
//lock.synMethodWithObj();
//類鎖方法,采用static synchronized increment的方式
LockTestClass.increment();
}
}
3.再編寫一個鎖的測試類LockTestClass,包括各種加鎖方法
public class LockTestClass {
//用于類鎖計數
private static int i = 0;
//私有鎖
private Object object = new Object();
/**
* <p>
* 無鎖方法
*
* @param threadID
* @param thread
*/
public void noSynMethod(long threadID, ObjThread thread) {
System.out.println("nosyn: class obj is " + thread + ", threadId is"
+ threadID);
}
/**
* 對象鎖方法1
*/
public synchronized void synOnMethod() {
System.out.println("synOnMethod begins" + ", time = "
+ System.currentTimeMillis() + "ms");
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("synOnMethod ends");
}
/**
* 對象鎖方法2,采用synchronized (this)來加鎖
*/
public void synInMethod() {
synchronized (this) {
System.out.println("synInMethod begins" + ", time = "
+ System.currentTimeMillis() + "ms");
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("synInMethod ends");
}
}
/**
* 對象鎖方法3
*/
public void synMethodWithObj() {
synchronized (object) {
System.out.println("synMethodWithObj begins" + ", time = "
+ System.currentTimeMillis() + "ms");
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("synMethodWithObj ends");
}
}
/**
* 類鎖
*/
public static synchronized void increament() {
System.out.println("class synchronized. i = " + i + ", time = "
+ System.currentTimeMillis() + "ms");
i++;
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("class synchronized ends.");
}
}
三、測試結果
1.測試類鎖和對象鎖,ObjectThread的run方法修改如下:
public void run() {
//無鎖方法
//lock.noSynMethod(this.getId(),this);
//對象鎖方法1,采用synchronized synInMethod的方式
lock.synInMethod();
//對象鎖方法2,采用synchronized(this)的方式
//lock.synOnMethod();
//私有鎖方法,采用synchronized(object)的方式
//lock.synMethodWithObj();
//類鎖方法,采用static synchronized increment的方式
LockTestClass.increament();
}
終端輸出:
start time = 1413101360231ms
synInMethod begins, time = 1413101360233ms
synInMethod ends
class synchronized. i = 0, time = 1413101362233ms
synInMethod begins, time = 1413101362233ms
class synchronized ends.
synInMethod ends
class synchronized. i = 1, time = 1413101364233ms
synInMethod begins, time = 1413101364233ms
class synchronized ends.
synInMethod ends
class synchronized. i = 2, time = 1413101366234ms
class synchronized ends.
可以看到對象鎖方法(synInMothod)第一次啟動時比類鎖方法(increament)快2秒,這是因為在synInMehtod執行時sleep了2秒再執行的increament,而這兩個方法共用一個線程,所以會慢2秒,如果increament在run中放到synInMethod前面,那么第一次啟動時就是increament快2秒。
而當類鎖方法啟動時,另一個線程時的對象鎖方法也幾乎同時啟動,說明二者使用的并非同一個鎖,不會產生競爭。
結論:類鎖和對象鎖不會產生競爭,二者的加鎖方法不會相互影響。
2.私有鎖和對象鎖,ObjectThread的run方法修改如下:
public void run() {
//無鎖方法
//lock.noSynMethod(this.getId(),this);
//對象鎖方法1,采用synchronized synInMethod的方式
lock.synInMethod();
//對象鎖方法2,采用synchronized(this)的方式
//lock.synOnMethod();
//私有鎖方法,采用synchronized(object)的方式
lock.synMethodWithObj();
//類鎖方法,采用static synchronized increment的方式
//LockTestClass.increament();
}
終端輸出:
start time = 1413121912406ms
synInMethod begins, time = 1413121912407ms.
synInMethod ends.
synMethodWithObj begins, time = 1413121914407ms
synInMethod begins, time = 1413121914407ms.
synInMethod ends.
synMethodWithObj ends
synInMethod begins, time = 1413121916407ms.
synMethodWithObj begins, time = 1413121916407ms
synInMethod ends.
synMethodWithObj ends
synMethodWithObj begins, time = 1413121918407ms
synMethodWithObj ends
和類鎖和對象鎖非常類似。
結論:私有鎖和對象鎖也不會產生競爭,二者的加鎖方法不會相互影響。
3.synchronized直接加在方法上和synchronized(this),ObjectThread的run方法修改如下:
public void run() {
//無鎖方法
//lock.noSynMethod(this.getId(),this);
//對象鎖方法1,采用synchronized synInMethod的方式
lock.synInMethod();
//對象鎖方法2,采用synchronized(this)的方式
lock.synOnMethod();
//私有鎖方法,采用synchronized(object)的方式
//lock.synMethodWithObj();
//類鎖方法,采用static synchronized increment的方式
//LockTestClass.increament();
}
終端輸出:
start time = 1413102913278ms
synInMethod begins, time = 1413102913279ms
synInMethod ends
synInMethod begins, time = 1413102915279ms
synInMethod ends
synOnMethod begins, time = 1413102917279ms
synOnMethod ends
synInMethod begins, time = 1413102919279ms
synInMethod ends
synOnMethod begins, time = 1413102921279ms
synOnMethod ends
synOnMethod begins, time = 1413102923279ms
synOnMethod ends
可以看到,二者嚴格地串行輸出(當然再次執行時先運行synInMethod還是先運行synOnMethod并不是確定的,取決于誰獲得了鎖)。
結論:synchronized直接加在方法上和synchronized(this)都是對當前對象加鎖,二者的加鎖方法夠成了競爭關系,同一時刻只能有一個方法能執行。
12、線程鎖
13、鎖粗化 多鎖變成一個,自己處理
15、鎖消除 偏向鎖就是鎖消除的一種
16、鎖膨脹 jvm實現,鎖粗化
17、信號量 使用阻塞鎖 實現的一種策略
新聞熱點
疑難解答