Java多執行緒中 synchronized和Lock的區別
我想很多購買了《Java程式設計師面試寶典》之類圖書的朋友一定對下面這個面試題感到非常熟悉:
問:請對比synchronized與java.util.concurrent.locks.Lock的異同。
答案:主要相同點:Lock能完成synchronized所實現的所有功能
主要不同點:Lock有比synchronized更精確的執行緒語義和更好的效能。synchronized會自動釋放鎖,而Lock一定要求程式設計師手工釋放,並且必須在finally從句中釋放。
恩,讓我們先鄙視一下應試教育。
言歸正傳,我們先來看一個多執行緒程式。它使用多個執行緒對一個Student物件進行訪問,改變其中的變數值。我們首先用傳統的synchronized 機制來實現它:
public
class Student {
privateint age =0;
publicint getAge() {
return age;
}
publicvoid setAge(int age) {
this.age = age;
}
}
Student student =new Student();
int count =0;
publicstaticvoid
ThreadDemo td =new ThreadDemo();
Thread t1 =new Thread(td, "a");
Thread t2 =new Thread(td, "b");
Thread t3 =new Thread(td, "c");
t1.start();
t2.start();
t3.start();
}
publicvoid run() {
accessStudent();
}
String currentThreadName = Thread.currentThread().getName();
System.out.println(currentThreadName +" is running!");
synchronized (this) {//(1)使用同一個ThreadDemo物件作為同步鎖 System.out.println(currentThreadName +" got [email protected]!");
try {
count++;
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(currentThreadName +" first Reading count:"+ count);
}
}
System.out.println(currentThreadName +" release [email protected]!");
synchronized (this) {//(2)使用同一個ThreadDemo物件作為同步鎖 System.out.println(currentThreadName +" got [email protected]!");
try {
Random random =new Random();
int age = random.nextInt(100);
System.out.println("thread "+ currentThreadName +" set age to:"+ age);
this.student.setAge(age);
System.out.println("thread "+ currentThreadName +" first read age is:"+this.student.getAge());
Thread.sleep(5000);
} catch (Exception ex) {
ex.printStackTrace();
} finally{
System.out.println("thread "+ currentThreadName +" second read age is:"+this.student.getAge());
}
}
System.out.println(currentThreadName +" release [email protected]!");
}
} 轉載註明出處:http://x-spirit.javaeye.com/、http://www.blogjava.net/zhangwei217245/
執行結果:
a is running!
a got [email protected]!
b is running!
c is running!
a first Reading count:1
a release [email protected]!
a got [email protected]!
thread a set age to:76
thread a first read age is:76
thread a second read age is:76
a release [email protected]!
c got [email protected]!
c first Reading count:2
c release [email protected]!
c got [email protected]!
thread c set age to:35
thread c first read age is:35
thread c second read age is:35
c release [email protected]!
b got [email protected]!
b first Reading count:3
b release [email protected]!
b got [email protected]!
thread b set age to:91
thread b first read age is:91
thread b second read age is:91
b release [email protected]!
成功生成(總時間:30 秒)
顯然,在這個程式中,由於兩段synchronized塊使用了同樣的物件做為物件鎖,所以JVM優先使剛剛釋放該鎖的執行緒重新獲得該鎖。這樣,每個執行緒執行的時間是10秒鐘,並且要徹底把兩個同步塊的動作執行完畢,才能釋放物件鎖。這樣,加起來一共是30秒。
轉載註明出處:http://x-spirit.javaeye.com/、http://www.blogjava.net/zhangwei217245/
我想一定有人會說:如果兩段synchronized塊採用兩個不同的物件鎖,就可以提高程式的併發性,並且,這兩個物件鎖應該選擇那些被所有執行緒所共享的物件。
那麼好。我們把第二個同步塊中的物件鎖改為student(此處略去程式碼,讀者自己修改),程式執行結果為:
a is running!
a got [email protected]!
b is running!
c is running!
a first Reading count:1
a release [email protected]!
a got [email protected]!
thread a set age to:73
thread a first read age is:73
c got [email protected]!
thread a second read age is:73
a release [email protected]!
c first Reading count:2
c release [email protected]!
c got [email protected]!
thread c set age to:15
thread c first read age is:15
b got [email protected]!
thread c second read age is:15
c release [email protected]!
b first Reading count:3
b release [email protected]!
b got [email protected]!
thread b set age to:19
thread b first read age is:19
thread b second read age is:19
b release [email protected]!
成功生成(總時間:21 秒)
從修改後的執行結果來看,顯然,由於同步塊的物件鎖不同了,三個執行緒的執行順序也發生了變化。在一個執行緒釋放第一個同步塊的同步鎖之後,第二個執行緒就可以進入第一個同步塊,而此時,第一個執行緒可以繼續執行第二個同步塊。這樣,整個執行過程中,有10秒鐘的時間是兩個執行緒同時工作的。另外十秒鐘分別是第一個執行緒執行第一個同步塊的動作和最後一個執行緒執行第二個同步塊的動作。相比較第一個例程,整個程式的執行時間節省了1/3。細心的讀者不難總結出優化前後的執行時間比例公式:(n+1)/2n,其中n為執行緒數。如果執行緒數趨近於正無窮,則程式執行效率的提高會接近50%。而如果一個執行緒的執行階段被分割成m個synchronized塊,並且每個同步塊使用不同的物件鎖,而同步塊的執行時間恆定,則執行時間比例公式可以寫作:((m-1)n+1)/mn那麼當m趨於無窮大時,執行緒數n趨近於無窮大,則程式執行效率的提升幾乎可以達到100%。(顯然,我們不能按照理想情況下的數學推導來給BOSS發報告,不過通過這樣的數學推導,至少我們看到了提高多執行緒程式併發性的一種方案,而這種方案至少具備數學上的可行性理論支援。)
轉載註明出處:http://x-spirit.javaeye.com/、http://www.blogjava.net/zhangwei217245/
可見,使用不同的物件鎖,在不同的同步塊中完成任務,可以使效能大大提升。
很多人看到這不禁要問:這和新的Lock框架有什麼關係?
彆著急。我們這就來看一看。
synchronized塊的確不錯,但是他有一些功能性的限制:
1.它無法中斷一個正在等候獲得鎖的執行緒,也無法通過投票得到鎖,如果不想等下去,也就沒法得到鎖。
2.synchronized塊對於鎖的獲得和釋放是在相同的堆疊幀中進行的。多數情況下,這沒問題(而且與異常處理互動得很好),但是,確實存在一些更適合使用非塊結構鎖定的情況。
轉載註明出處:http://x-spirit.javaeye.com/、http://www.blogjava.net/zhangwei217245/
java.util.concurrent.lock 中的 Lock 框架是鎖定的一個抽象,它允許把鎖定的實現作為 Java類,而不是作為語言的特性來實現。這就為 Lock 的多種實現留下了空間,各種實現可能有不同的排程演算法、效能特性或者鎖定語義。
JDK官方文件中提到:
ReentrantLock是“一個可重入的互斥鎖 Lock,它具有與使用 synchronized 方法和語句所訪問的隱式監視器鎖相同的一些基本行為和語義,但功能更強大。
ReentrantLock 將由最近成功獲得鎖,並且還沒有釋放該鎖的執行緒所擁有。當鎖沒有被另一個執行緒所擁有時,呼叫 lock的執行緒將成功獲取該鎖並返回。如果當前執行緒已經擁有該鎖,此方法將立即返回。可以使用 isHeldByCurrentThread() 和getHoldCount() 方法來檢查此情況是否發生。 ”
簡單來說,ReentrantLock有一個與鎖相關的獲取計數器,如果擁有鎖的某個執行緒再次得到鎖,那麼獲取計數器就加1,然後鎖需要被釋放兩次才能獲得真正釋放。這模仿了synchronized 的語義;如果執行緒進入由執行緒已經擁有的監控器保護的 synchronized塊,就允許執行緒繼續進行,當執行緒退出第二個(或者後續) synchronized 塊的時候,不釋放鎖,只有執行緒退出它進入的監控器保護的第一個synchronized 塊時,才釋放鎖。
轉載註明出處:http://x-spirit.javaeye.com/、http://www.blogjava.net/zhangwei217245/
ReentrantLock 類(重入鎖)實現了 Lock ,它擁有與 synchronized相同的併發性和記憶體語義,但是添加了類似鎖投票、定時鎖等候和可中斷鎖等候的一些特性。此外,它還提供了在激烈爭用情況下更佳的效能。(換句話說,當許多執行緒都想訪問共享資源時,JVM 可以花更少的時候來排程執行緒,把更多時間用在執行執行緒上。)
我們把上面的例程改造一下:
publicclass ThreadDemo implements Runnable {
class Student {
privateint age =0;
publicint getAge() {
return age;
}
publicvoid setAge(int age) {
this.age = age;
}
}
Student student =new Student();
int count =0;
ReentrantLock lock1 =new ReentrantLock(false);
ReentrantLock lock2 =new ReentrantLock(false);
publicstaticvoid main(String[] args) {
ThreadDemo td =new ThreadDemo();
for (int i =1; i <=3; i++) {
Thread t =new Thread(td, i +"");
t.start();
}
}
publicvoid run() {
accessStudent();
}
publicvoid accessStudent() {
String currentThreadName = Thread.currentThread().getName();
System.out.println(currentThreadName +" is running!");
lock1.lock();//使用重入鎖 System.out.println(currentThreadName +" got [email protected]!");
try {
count++;
Thread.sleep(5000);
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println(currentThreadName +" first Reading count:"+ count);
lock1.unlock();
System.out.println(currentThreadName +" release [email protected]!");
}
lock2.lock();//使用另外一個不同的重入鎖 System.out.println(currentThreadName +" got [email protected]!");
try {
Random random =new Random();
int age = random.nextInt(100);
System.out.println("thread "+ currentThreadName +" set age to:"+ age);
this.student.setAge(age);
System.out.println("thread "+ currentThreadName +" first read age is:"+this.student.getAge());
Thread.sleep(5000);
} catch (Exception ex) {
ex.printStackTrace();
} finally {
System.out.println("thread "+ currentThreadName +" second read age is:"+this.student.getAge());
lock2.unlock();
System.out.println(currentThreadName +" release [email protected]!");
}
}
}
從上面這個程式我們看到:
物件鎖的獲得和釋放是由手工編碼完成的,所以獲得鎖和釋放鎖的時機比使用同步塊具有更好的可定製性。並且通過程式的執行結果(執行結果忽略,請讀者根據例程自行觀察),我們可以發現,和使用同步塊的版本相比,結果是相同的。
轉載註明出處:http://x-spirit.javaeye.com/、http://www.blogjava.net/zhangwei217245/
這說明兩點問題:
1. 新的ReentrantLock的確實現了和同步塊相同的語義功能。而物件鎖的獲得和釋放都可以由編碼人員自行掌握。
2. 使用新的ReentrantLock,免去了為同步塊放置合適的物件鎖所要進行的考量。
3.使用新的ReentrantLock,最佳的實踐就是結合try/finally塊來進行。在try塊之前使用lock方法,而在finally中使用unlock方法。
轉載註明出處:http://x-spirit.javaeye.com/、http://www.blogjava.net/zhangwei217245/
細心的讀者又發現了:
在我們的例程中,建立ReentrantLock例項的時候,我們的建構函式裡面傳遞的引數是false。那麼如果傳遞true又回是什麼結果呢?這裡面又有什麼奧祕呢?
請看本節的續 ———— Fair or Unfair? It is aquestion...
Lock是java.util.concurrent.locks包下的介面,Lock 實現提供了比使用synchronized 方法和語句可獲得的更廣泛的鎖定操作,它能以更優雅的方式處理執行緒同步問題,我們拿Java執行緒(二)中的一個例子簡單的實現一下和sychronized一樣的效果,程式碼如下: [java] view plaincopyprint?
![在CODE上檢視程式碼片](https://code.csdn.net/assets/CODE_ico.png)
- publicclass LockTest {
- publicstaticvoid main(String[] args) {
- final Outputter1 output = new Outputter1();
- new Thread() {
- publicvoid run() {
- output.output("zhangsan");
- };
- }.start();
- new Thread() {
- publicvoid run() {
- output.output("lisi");
- };
- }.start();
- }
- }
- class Outputter1 {
- private Lock lock = new ReentrantLock();// 鎖物件
- publicvoid output(String name) {
- // TODO 執行緒輸出方法
- lock.lock();// 得到鎖
- try {
- for(int i = 0; i < name.length(); i++) {
- System.out.print(name.charAt(i));
- }
- } finally {
- lock.unlock();// 釋放鎖
- }
- }
- }
如果說這就是Lock,那麼它不能成為同步問題更完美的處理方式,下面要介紹的是讀寫鎖(ReadWriteLock),我們會有一種需求,在對資料進行讀寫的時候,為了保證資料的一致性和完整性,需要讀和寫是互斥的,寫和寫是互斥的,但是讀和讀是不需要互斥的,這樣讀和讀不互斥效能更高些,來看一下不考慮互斥情況的程式碼原型:
[java] view plaincopyprint?![在CODE上檢視程式碼片](https://code.csdn.net/assets/CODE_ico.png)
- publicclass ReadWriteLockTest {
- publicstaticvoid main(String[] args) {
- final Data data = new Data();
- for (int i = 0; i < 3; i++) {
- new Thread(new Runnable() {
- publicvoid run() {
- for (int j = 0; j < 5; j++) {
- data.set(new Random().nextInt(30));
- }
- }
- }).start();
- }
- for (int i = 0; i < 3; i++) {
- new Thread(new Runnable() {
- publicvoid run() {
- for (int j = 0; j < 5; j++) {
- data.get();
- }
- }
- }).start();
- }
- }
- }
- class Data {
- privateint data;// 共享資料
- publicvoid set(int data) {
- System.out.println(Thread.currentThread().getName() + "準備寫入資料");
- try {
- Thread.sleep(20);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- this.data = data;
- System.out.println(Thread.currentThread().getName() + "寫入" + this.data);
- }
- publicvoid get() {
- System.out.println(Thread.currentThread().getName() + "準備讀取資料");
- try {
- Thread.sleep(20);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + "讀取" + this.data);
- }
- }
![在CODE上檢視程式碼片](https://code.csdn.net/assets/CODE_ico.png)
- Thread-1準備寫入資料
- Thread-3準備讀取資料
- Thread-2準備寫入資料
- Thread-0準備寫入資料
- Thread-4準備讀取資料
- Thread-5準備讀取資料
- Thread-2寫入12
- Thread-4讀取12
- Thread-5讀取5
- Thread-1寫入12
![在CODE上檢視程式碼片](https://code.csdn.net/assets/CODE_ico.png)
- publicsynchronizedvoid set(int data) {...}
- publicsynchronizedvoid get() {...}
[java] view plaincopyprint?
![在CODE上檢視程式碼片](https://code.csdn.net/assets/CODE_ico.png)
- Thread-0準備寫入資料
- Thread-0寫入9
- Thread-5準備讀取資料
- Thread-5讀取9
- Thread-5準備讀取資料
- Thread-5讀取9
- Thread-5準備讀取資料
- Thread-5讀取9
- Thread-5準備讀取資料
- Thread-5讀取9
![在CODE上檢視程式碼片](https://code.csdn.net/assets/CODE_ico.png)
- class Data {
- privateint data;// 共享資料
- private ReadWriteLock rwl = new ReentrantReadWriteLock();
- publicvoid set(int data) {
- rwl.writeLock().lock();// 取到寫鎖
- try {
- System.out.println(Thread.currentThread().getName() + "準備寫入資料");
- try {
- Thread.sleep(20);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- this.data = data;
- System.out.println(Thread.currentThread().getName() + "寫入" + this.data);
- } finally {
- rwl.writeLock().unlock();// 釋放寫鎖
- }
- }
- publicvoid get() {
- rwl.readLock().lock();// 取到讀鎖
- try {
- System.out.println(Thread.currentThread().getName() + "準備讀取資料");
- try {
- Thread.sleep(20);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(Thread.currentThread().getName() + "讀取" + this.data);
- } finally {
- rwl.readLock().unlock();// 釋放讀鎖
- }
- }
- }
部分輸出結果:
[java] view plaincopyprint?![在CODE上檢視程式碼片](https://code.csdn.net/assets/CODE_ico.png)
- Thread-4準備讀取資料
- Thread-3準備讀取資料
- Thread-5準備讀取資料
- Thread-5讀取18
- Thread-4讀取18
- Thread-3讀取18
- Thread-2準備寫入資料
- Thread-2寫入6
- Thread-2準備寫入資料
- Thread-2寫入10
- Thread-1準備寫入資料
- Thread-1寫入22
- Thread-5準備讀取資料
從結果可以看出實現了我們的需求,這只是鎖的基本用法,鎖的機制還需要繼續深入學習。
本文來自:高爽|Coder,原文地址:http://blog.csdn.net/ghsau/article/details/7461369,轉載請註明。相關推薦
Java多執行緒中 synchronized和Lock的區別
我們已經瞭解了Java多執行緒程式設計中常用的關鍵字synchronized,以及與之相關的物件鎖機制。這一節中,讓我們一起來認識JDK 5中新引入的併發框架中的鎖機制。 我想很多購買了《Java程式設計師面試寶典》之類圖書的朋友一定對下面這個面試題感到非常熟悉: 問:請對比synchronized與java
Java多執行緒中Synchronized簡介和Static Synchronized的區別
在進行Java開發時,多執行緒的開發是經常會使用的。首先會問一個小問題啊,在Java中有幾種方法可以建立一個執行緒? 我給的答案是3種。(如果還有其他的請留言告訴我哈。) 1、建立直接繼承自Thread類建立執行緒子類。 步驟如下:a 定義一個子類,同時
Java多執行緒中start()和run()的區別
Java的執行緒是通過java.lang.Thread類來實現的。VM啟動時會有一個由主方法所定義的執行緒。可以通過建立Thread的例項來建立新的執行緒。每個執行緒都是通過某個特定Thread物件所對應的方法run()來完成其操作的,方法run()稱為執行緒體。通過呼叫Thread類的start(
java多執行緒之synchronized與lock、wait與notify
class Res { public String name; public String sex; public Boolean flag = false; public Lock lock = new ReentrantLock(); Condition condition = lock.new
java多執行緒之synchronized和volatile關鍵字
synchronized同步方法 髒讀 在多個執行緒對同一個物件中的例項變數進行併發訪問的時候,取到的資料可能是被更改過的,稱之為“髒讀”,這就是非執行緒安全的。解決的方法為synchronized關鍵字進行同步,使之操作變成同步而非非同步。 public
多執行緒中wait和sleep區別
wiat和sleep的區別? 1、wait可以指定時間也可以不指定 sleep必須指定時間。 2、在同步中,對cpu的執行權和鎖的處理不同 wait:釋 放執行權,釋放鎖。 slee
java執行緒學習(一): 多執行緒中start()和run()的區別
趁著有空,看看執行緒Thread的原始碼,挺有意思的 這裡來說說多執行緒中start()和run()的區別。 1-跟start()有關的原始碼: public class Thread implements Runnable { private ThreadGroup group;
Java多執行緒之—Synchronized方式和CAS方式實現執行緒安全效能對比
效能比較猜想 1.大膽假設 在設計試驗方法之前,針對Synchronized和CAS兩種方式的特點,我們先來思考一下兩種方式效率如何? 首先,我們在回顧一下兩種方式是如何保證執行緒安全的。Synchronized方式通過大家應該很熟悉,他的行為非常悲觀,只要有一個執行緒進
Java 內部類,多執行緒中Synchronized與wait,notify的使用
工作內容: 1.成員內部類 與成員方法,屬性的訪問許可權一致 2.靜態內部類 修飾符 stactic 類名{...} 3.匿名內部類 new 類名()/介面名(重寫介面方法) 4.區域性內部類 程式碼塊中 5.執行緒Thread wait,noti
java多執行緒中的sleep()、wait()、notify()和物件鎖的關係
1、sleep()不釋放物件鎖。 2、wait()釋放物件鎖。 3、notify()不釋放物件鎖。 (1)、notify釋放鎖嗎?不要誤導別人。notifty()只是喚醒此物件監視器上等待的單個執行緒,直到當前執行緒釋放此物件上的鎖,才有可能繼續執行被喚醒的執行緒。 (2)
Java多執行緒中的final和static
看Android的多執行緒發現其實是Java的多執行緒。我找了一本Java程式設計思想學習Java的併發機制。寫了一個demo,遇到一些問題,雖然最後想明白了,但是也暴露了我的Java基礎差勁的事實。之後我會通過寫部落格的方式來提高Java水平。現在說一下我的問
Java多執行緒中避免在多生產者和多消費者場景中出現假死
在多執行緒程式設計中,如果所有執行緒全部都經由wait()方法進入等待狀態,那麼程式就進入了假死狀態 程式示例 考慮這個例子,來自《Java多執行緒程式設計核心技術》: 生產者類P: //生產者 public class P { private Stri
Java多執行緒中的阻塞佇列和併發集合
本章主要探討在多執行緒程式中與集合相關的內容。在多執行緒程式中,如果使用普通集合往往會造成資料錯誤,甚至造成程式崩潰。Java為多執行緒專門提供了特有的執行緒安全的集合類,通過下面的學習,您需要掌握這些集合的特點是什麼,底層實現如何、在何時使用等問題。 3.1 Blo
Java多執行緒中,Join和Interrupt()方法的使用
更多詳細的解答請轉至:http://my.oschina.net/summerpxy/blog/198457;http://uule.iteye.com/blog/1101994;(比如有一個執行緒t.當在Main執行緒中呼叫t.join()的時候,那麼Main執行緒必須拿
Java多執行緒中的虛假喚醒和如何避免
## 先來看一個例子 一個賣面的麵館,有一個做面的廚師和一個吃麵的食客,需要保證,廚師做一碗麵,食客吃一碗麵,不能一次性多做幾碗面,更不能沒有面的時候吃麵;按照上述操作,進行十輪做面吃麵的操作。 ## 用程式碼說話 首先我們需要有一個資源類,裡面包含面的數量,做面操作,吃麵操作; 當面的
多執行緒2-synchronized、lock
1、什麼時候會出現執行緒安全問題? 在多執行緒程式設計中,可能出現多個執行緒同時訪問同一個資源,可以是:變數、物件、檔案、資料庫表等。此時就存在一個問題: 每個執行緒執行過程是不可控的,可能導致最終結果與實際期望結果不一致或者直接導致程式出錯。 如我們在第一篇部落格中出現的count--的問
Java多執行緒-44-靜態和非靜態方法同步鎖物件是什麼
前面一篇,我們知道了synchronized關鍵字擴起來範圍的程式碼塊就可以實現同步,其實,在Java中,只需要在方法上加上synchronized關鍵字即可,就像加上static一樣。本篇來看看加上synchronized關鍵字修飾的非靜態和靜態方法的同步鎖物件是什麼。 1.非靜態同步鎖物
java多執行緒中的異常處理
分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!  
java多執行緒中顯式鎖的輪詢檢測策略
顯式鎖簡介 java5.0之前,在協調對共享物件的訪問時可以使用的機制只有synchronized和volatile,java5.0增加了一種新的機制:ReentrantLock。 鎖像synchronized同步塊一樣,是一種執行緒同步機制,與synchronized不同的是ReentrantLock提
Java多執行緒學習---Condition和wait、notify(十三)
1.問題:實現兩個執行緒交叉執行(Condition和wait、notify都可以實現) public class ConditionStudy { public static void main(String[] args) { //執行緒程式碼 BussinessTes