多個執行線程共享一個資源的情景,是最多見的併發編程情景之一。在併發應用中經常遇到這樣的情景:多個線程讀或者寫相同的數據,或者訪問相同的文件或者數據庫鏈接。java
爲了防止這些共享資源可能出現的錯誤或數據不一致,咱們必須實現一些機制來防止這些錯誤的發生。數據庫
爲了解決這些問題,人們引入了臨界區概念。臨界區是一個可用以訪問共享資源的代碼塊,這個代碼塊在同一時間內只容許一個線程執行。編程
爲了幫助編程人員實行這個臨界區,java提供了同步機制。當一個線程試圖訪問一個臨界區時,它將使用一種同步機制來查看是否是已經有其餘線程進入臨界區。若是沒有其餘線程進入臨界區,它就能夠進入臨界區;若是已經有線程進入了臨界區,它就被同步機制掛起,直到進入的線程離開這個臨界區。若是在等待進入臨界區的線程不止一個,JVM會選擇其中一個,其他的將繼續等待。安全
java語言提供了兩種基本同步機制:數據結構
1.synchronized關鍵字機制多線程
2.Lock接口及其實現機制併發
1、synchronized關鍵字機制dom
1.同步方法
即有synchronized關鍵字修飾的方法。
因爲java的每一個對象都有一個內置鎖,當用此關鍵字修飾方法時,內置鎖會保護整個方法。在調用該方法前,須要得到內置鎖,不然就處於阻塞狀態。
代碼如:
public synchronized void save(){}
對於實例的同步方法,使用this即當前實例對象。
對於靜態的同步方法,使用當前類的字節碼對象。
注: synchronized關鍵字也能夠修飾靜態方法,此時若是調用該靜態方法,將會鎖住整個類。
2.同步代碼塊
即有synchronized關鍵字修飾的語句塊。
被該關鍵字修飾的語句塊會自動被加上內置鎖,從而實現同步
代碼如:
synchronized(object){}
Java中任意的對象均可以做爲一個監聽器(monitor),監聽器能夠被上鎖和解鎖,在線程同步中稱爲同步鎖,且同步鎖在同一時間只能被一個線程所持有。上面的obj對象就是一個同步鎖,分析一下上面代碼的執行過程:
1).一個線程執行到synchronized代碼塊,首先檢查obj,若是obj爲空,拋出NullPointerExpression異常;
2).若是obj不爲空,線程嘗試給監聽器上鎖,若是監聽器已經被鎖,則線程不能獲取到鎖,線程就被阻塞;
3).若是監聽器沒被鎖,則線程將監聽器上鎖,而且持有該鎖,而後執行代碼塊;
4).代碼塊正常執行結束或者非正常結束,監聽器都將自動解鎖;
線程同步鎖對多個線程必須是互斥的,即多個線程須要使用同一個同步鎖。代碼中obj對象被多個線程共享,可以實現同步。
注:同步是一種高開銷的操做,所以應該儘可能減小同步的內容。
一般沒有必要同步整個方法,使用synchronized代碼塊同步關鍵代碼便可。
二者的區別主要體如今同步鎖上面。對於實例的同步方法,由於只能使用this來做爲同步鎖,若是一個類中須要使用到多個鎖,爲了不鎖的衝突,必然須要使用不一樣的對象,這時候同步方法不能知足需求,只能使用同步代碼塊(同步代碼塊能夠傳入任意對象);或者多個類中須要使用到同一個鎖,這時候多個類的實例this顯然是不一樣的,也只能使用同步代碼塊,傳入同一個對象。ide
2、Lock接口及其實現機制高併發
Lock接口及實現類提供了更多的好處:
1.支持更靈活的同步代碼塊結構。使用synchronized關鍵字時,只能在同一個synchronized塊結構中獲取和釋放控制。Lock接口容許實現更復雜的臨界區結構,即控制的獲取和釋放不出如今同一個塊結構中。
2.相比synchronized關鍵字,Lock接口提供了更多的功能。其中一個新功能是tryLock()方法的實現。這個方法試圖獲取鎖,若是鎖已被其餘線程獲取,它將返回false,並繼續往下執行代碼。使用synchronized關鍵字時,若是線程A試圖執行一個同步代碼塊,而線程B已在執行這個同步代碼塊,則線程A就會被掛起直到線程B運行完這個同步代碼塊。使用鎖tryLock()方法,經過返回值將得知是否有其餘線程正在使用這個鎖保護的代碼塊。
3.Lock接口容許分離讀和寫操做,容許多個讀線程和只有一個寫線程。
4.相比synchronized關鍵字,Lock接口具備更好的性能。
鎖的公平性
ReentrantLock和ReentrantReadWriteLock類的構造器都含有一個布爾參數fair,它容許你控制着兩個類的行爲。默認fair值爲false,它稱爲非公平模式。
在非公平模式下,當有不少線程在等待鎖時,鎖將選擇它們中的一個來訪問臨界區,這個選擇是沒有任何約束的。
若是fair值爲true,則稱爲公平模式。
在公平模式下,當有不少線程在等待鎖時,鎖將選擇它們中的一個來訪問臨界區,並且選擇的是等待時間最長的。
這兩種模式只適用於lock()和unlock()方法。而Lock接口的tryLock()方法沒有將線程置於休眠,fair屬性並不影響這個方法。
經常使用的線程類:
1)、ReentrantLock類:是一個可重入的互斥鎖,重入鎖是一種遞歸無阻塞的同步機制。ReentrantLock由最近成功獲取鎖,尚未釋放的線程所擁有,當鎖被另外一個線程擁有時,調用lock的線程能夠成功獲取鎖。若是鎖已經被當前線程擁有,當前線程會當即返回。
a、防止重複執行(忽略重複觸發)
ReentrantLock lock = new ReentrantLock();
public void getObject(){
//若是已經被lock,則當即返回false不會等待,達到忽略操做的效果
if (lock.tryLock()) {
try {
//操做
} finally {
lock.unlock();
}
}
}
b、同步執行,相似synchronized
ReentrantLock lock = new ReentrantLock(); //參數默認false,不公平鎖
//ReentrantLock lock = new ReentrantLock(true); //公平鎖
public void getObject(){
//若是被其它資源鎖定,會在此等待鎖釋放,達到暫停的效果
lock.lock();
try {
//操做
} finally {
lock.unlock();
}
}
c、嘗試等待執行
ReentrantLock lock = new ReentrantLock(true); //公平鎖
public void getObject(){
try {
if (lock.tryLock(5, TimeUnit.SECONDS)) {
//若是已經被lock,嘗試等待5s,看是否能夠得到鎖,若是5s後仍然沒法得到鎖則返回false繼續執行
try {
//操做
} finally {
lock.unlock();
}
}
} catch (InterruptedException e) {
e.printStackTrace(); //當前線程被中斷時(interrupt),會拋InterruptedException
}
}
d、可中斷鎖的同步執行
ReentrantLock lock = new ReentrantLock(true); //公平鎖
public void getObject(){
lock.lockInterruptibly();
try {
//操做
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
ReadWriteLock類:讀寫鎖,維護了一對相關的鎖,一個用於只讀操做,一個用於寫入操做。只要沒有writer,讀取鎖能夠由多個reader線程同時保持。寫入鎖是獨佔的。
ReentrantReadWriteLock類:可重入讀寫鎖,會使用兩把鎖來解決問題,一個讀鎖,一個寫鎖。
線程進入讀鎖的前提條件:
1).沒有其餘線程的寫鎖,
2).沒有寫請求或者有寫請求,但調用線程和持有鎖的線程是同一個
線程進入寫鎖的前提條件:
1).沒有其餘線程的讀鎖
2).沒有其餘線程的寫鎖
ReentrantReadWriteLock和ReentrantLock的區別,它和後者都是單獨的實現,彼此之間沒有繼承或實現的關係:
(a).重入方面其內部的WriteLock能夠獲取ReadLock,可是反過來ReadLock想要得到WriteLock則永遠都不要想。
(b).WriteLock能夠降級爲ReadLock,順序是:先得到WriteLock再得到ReadLock,而後釋放WriteLock,這時候線程將保持Readlock的持有。反過來ReadLock想要升級爲WriteLock則不可能。
(c).ReadLock能夠被多個線程持有而且在做用時排斥任何的WriteLock,而WriteLock則是徹底的互斥。這一特性最爲重要,由於對於高讀取頻率而相對較低寫入的數據結構,使用此類鎖同步機制則能夠提升併發量。
(d).不論是ReadLock仍是WriteLock都支持Interrupt,語義與ReentrantLock一致。
(e).WriteLock支持Condition而且與ReentrantLock語義一致,而ReadLock則不能使用Condition,不然拋出UnsupportedOperationException異常。
讀寫鎖的例子:
import java.util.Random;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockTest {
public static void main(String[] args) {
Queue3 q3 = new Queue3();
for(int i=0;i<3;i++){
new Thread(){
public void run(){
while(true){
q3.get();
}
}
}.start();
}
for(int i=0;i<3;i++){
new Thread(){
public void run(){
while(true){
q3.put(new Random().nextInt(10000));
}
}
}.start();
}
}
}
class Queue3{
private Object data = null;//共享數據,只能有一個線程能寫該數據,但能夠有多個線程同時讀該數據。
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public void get(){
rwl.readLock().lock();//上讀鎖,其餘線程只能讀不能寫
System.out.println(Thread.currentThread().getName() + " be ready to read data!");
try {
Thread.sleep((long)(Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "have read data :" + data);
rwl.readLock().unlock(); //釋放讀鎖,最好放在finnaly裏面
}
public void put(Object data){
rwl.writeLock().lock();//上寫鎖,不容許其餘線程讀也不容許寫
System.out.println(Thread.currentThread().getName() + " be ready to write data!");
try {
Thread.sleep((long)(Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
this.data = data;
System.out.println(Thread.currentThread().getName() + " have write data: " + data);
rwl.writeLock().unlock();//釋放寫鎖
}
}
在鎖中使用多條件
一個鎖可能關聯一個或多個條件,這些條件經過Condition接口聲明。目的是容許線程獲取鎖而且查看等待的某一個條件是否知足,若是不知足就掛起直到某個線程喚醒它們。Condition接口提供了掛起線程和喚起線程的機制。
Condition是在java1.5中才出現的,它用來替代傳統的Object的wait()、notify()實現線程間的協做,相比使用Object的wait()、notify(),使用Condition1的await()、signal()這種方式實現線程間協做更加安全和高效。Condition是個接口,基本的方法就是await()和signal()方法;Condition依賴於Lock接口,生成一個Condition的基本代碼是lock.newCondition()調用Condition的await()和signal()方法,都必須在lock保護以內,就是說必須在lock.lock()和lock.unlock之間纔可使用Conditon中的await()對應Object的wait();Condition中的signal()對應Object的notify();Condition中的signalAll()對應Object的notifyAll()。代碼示例:import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class Main { public static void main(String[] args) { final ReentrantLock reentrantLock = new ReentrantLock(); final Condition condition = reentrantLock.newCondition(); new Thread(new Runnable() { @Override public void run() { reentrantLock.lock(); System.out.println(Thread.currentThread().getName() + "拿到鎖了"); System.out.println(Thread.currentThread().getName() + "等待信號"); try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "拿到信號"); reentrantLock.unlock(); } }, "線程1").start(); new Thread(new Runnable() { @Override public void run() { reentrantLock.lock(); System.out.println(Thread.currentThread().getName() + "拿到鎖了"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "發出信號"); condition.signalAll(); reentrantLock.unlock(); } }, "線程2").start(); } }