鎖是用來控制多個線程訪問同一個共享資源的方式,通常來講,一個鎖能防止多個線程同時訪問共享資源,在Lock接口出來以前,Java是經過synchronized關鍵字來實現鎖的功能,而Java1.5以後,併發包新增了Lock接口(以及相關實現類)用來實現鎖的功能,它提供了與synchronized關鍵字相似的同步功能,只是在使用方式上有所不一樣,須要顯式的獲取鎖和釋放鎖。雖然缺乏了隱式的便捷性,但卻擁有了鎖獲取和釋放的可操做性,可中斷的獲取因此及超時獲取鎖的的同步特性java
特性 | 描述 |
---|---|
嘗試非阻塞式獲取鎖 | 當前線程嘗試獲取鎖,若是這一刻沒有被其餘線程獲取到,則成功獲取並持有鎖 |
能被中斷的獲取鎖 | 與synchronized關鍵字不一樣,獲取到鎖的線程可以響應中斷,當獲取到鎖的線程被中斷時,中斷異常會被拋出,同時鎖會被釋放 |
超時獲取鎖 | 在指定的截止時間以前獲取到鎖,入夥截止時間到了仍舊沒法獲取鎖,則返回 |
方法名稱 | 描述 |
---|---|
void lock() | 獲取鎖,調用該方法當前線程將會獲取鎖,當鎖獲取到時,從該方法返回 |
void lockInterruptibly() throws InterruptedException() | 可中斷的獲取鎖,和lock()方法的不一樣之處在於該方法可響應中斷,即在鎖的獲取中和中斷當前線程 |
boolean tryLock() | 嘗試非阻塞的獲取鎖,調用該方法馬上返回,若是可以獲取則返回true,不然返回false |
boolean tryLock(long time,TimeUnit unit) throws InterruptedException() | 超時的獲取鎖,當前線程在如下三種狀況會返回:1.當前線程在超時時間內獲取到鎖 2. 當前線程在超時時間內被中斷 3. 超時時間結束,返回false |
void unlock() | 釋放鎖 |
Condition newCondition() | 獲取等待通知組件,該組件和當前的鎖綁定,當前線程只有得到了鎖,才能調用該組件的wait()方法,而調用後,當前線程釋放鎖 |
如下簡稱AQS AQS是用來構建鎖和其餘同步組件的基礎框架,它使用了一個int成員變量表示同步狀態,經過內置的FIFO隊列來完成資源獲取線程排隊工做redis
AQS給予模板方法設計模式設計的,也就是說,使用者須要繼承AQS並重寫指定的方法進行實現算法
AQS提供以下三個方法來訪問和修改同步狀態:數據庫
方法名稱 | 描述 |
---|---|
protected boolean tryAcquire(int arg) | 獨佔式的獲取同步狀態,實現該方法須要查詢當前狀態並判斷同步狀態是否符合預期,而後再進行CAS設置同步狀態 |
protected boolean tryRelease(int arg) | 獨佔式釋放同步狀態,等待獲取同步狀態的線程將有機會獲取同步狀態 |
protected int tryAcquireShared(int arg) | 共享式獲取同步狀態,返回大於等於0的值,表示獲取成功,反之獲取失敗 |
protected boolean tryReleaseShared(int arg) | 共享式釋放同步狀態 |
protected boolean isHeldExclusively() | 當前同步器是否在獨佔模式下被線程佔用,通常該方法表示是否被當前線程所獨佔 |
方法名稱 | 描述 |
---|---|
void acquire(int arg) | 獨佔式獲取同步狀態,若是當前線程獲取同步狀態成功,則該方法返回,不然,將進入同步隊列等待,該方法將會調用重寫的tryAcquire(int arg)方法 |
void acquireInterruptibly(int arg) | 與acquire(int arg)相同,可是該方法響應中斷,當前線程未獲取到同步狀態而進入同步隊列中,若是當前線程被中斷,則該方法拋出異常並返回 |
boolean tryAcquireNanos(int arg,long nanos) | 在acquireInterruptibly(int arg)基礎上增長了超時限制,若是當前線程在超時時間內沒有獲取到同步狀態,那麼將會返回false,若是獲取到了則返回true |
void acquireShared(int arg) | 共享式的獲取同步狀態,若是當前線程未獲取到同步狀態,將會進入同步隊列中進行等待,與獨佔式的區別主要在於同一時刻能夠有多個線程獲取到同步狀態 |
void acquireSharedInterruptibly(int arg) | 與acquireInterruptibly(int arg)相同,該方法可響應中斷 |
boolean tryAcquireSharedNanos(int arg,long nanos) | 在acquireSharedInterruptibly(int arg)基礎上增長了超時限制 |
boolean release(int arg) | 獨佔式的釋放同步狀態,該方法會在釋放同步狀態以後,將同步隊列中的第一個節點包含的線程喚醒 |
boolean releaseShared(int arg) | 共享式的釋放同步狀態 |
Collection getQueueThreads() | 獲取等待在同步隊列上的線程集合 |
重入鎖,也叫作遞歸鎖,指的是同一線程外層函數得到鎖以後,內層遞歸函數仍然有獲取該鎖的代碼,但不受影響。編程
在JAVA環境下ReentrantLock和sypnchronized都是可重入鎖設計模式
public class Test implements Runnable {
public synchronized void get() {
System.out.println("name:" + Thread.currentThread().getName() + " get();");
set();
}
public synchronized void set() {
System.out.println("name:" + Thread.currentThread().getName() + " set();");
}
@Override
public void run() {
get();
}
public static void main(String[] args) {
Test ss = new Test();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
}
}
複製代碼
public class Test02 extends Thread {
ReentrantLock lock = new ReentrantLock();
public void get() {
lock.lock();
System.out.println(Thread.currentThread().getId());
set();
lock.unlock();
}
public void set() {
lock.lock();
System.out.println(Thread.currentThread().getId());
lock.unlock();
}
@Override
public void run() {
get();
}
public static void main(String[] args) {
Test ss = new Test();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
}
}
複製代碼
相比Java中的鎖(Locks in Java)裏Lock實現,讀寫鎖更復雜一些。假設你的程序中涉及到對一些共享資源的讀和寫操做,且寫操做沒有讀操做那麼頻繁。在沒有寫操做的時候,兩個線程同時讀一個資源沒有任何問題,因此應該容許多個線程能在同時讀取共享資源。可是若是有一個線程想去寫這些共享資源,就不該該再有其它線程對該資源進行讀或寫(譯者注:也就是說:讀-讀能共存,讀-寫不能共存,寫-寫不能共存)。 這就須要一個讀/寫鎖來解決這個問題。Java5在java.util.concurrent包中已經包含了讀寫鎖。儘管如此,咱們仍是應該瞭解其實現背後的原理。緩存
public class Cache {
static Map<String, Object> map = new HashMap<String, Object>();
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
static Lock r = rwl.readLock();
static Lock w = rwl.writeLock();
// 獲取一個key對應的value
public static final Object get(String key) {
r.lock();
try {
System.out.println("正在作讀的操做,key:" + key + " 開始");
Thread.sleep(100);
Object object = map.get(key);
System.out.println("正在作讀的操做,key:" + key + " 結束");
System.out.println();
return object;
} catch (InterruptedException e) {
} finally {
r.unlock();
}
return key;
}
// 設置key對應的value,並返回舊有的value
public static final Object put(String key, Object value) {
w.lock();
try {
System.out.println("正在作寫的操做,key:" + key + ",value:" + value + "開始.");
Thread.sleep(100);
Object object = map.put(key, value);
System.out.println("正在作寫的操做,key:" + key + ",value:" + value + "結束.");
System.out.println();
return object;
} catch (InterruptedException e) {
} finally {
w.unlock();
}
return value;
}
// 清空全部的內容
public static final void clear() {
w.lock();
try {
map.clear();
} finally {
w.unlock();
}
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
Cache.put(i + "", i + "");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
Cache.get(i + "");
}
}
}).start();
}
}
複製代碼
老是認爲不會產生併發問題,每次去取數據的時候總認爲不會有其餘線程對數據進行修改,所以不會上鎖,可是在更新時會判斷其餘線程在這以前有沒有對數據進行修改,通常會使用版本號機制或CAS操做實現,本質沒有鎖,效率比較高,無阻塞,無等待,重試安全
實現方式服務器
update table set x=x+1, version=version+1 where id=#{id} and version=#{version};
複製代碼
老是假設最壞的狀況,每次取數據時都認爲其餘線程會修改,因此都會加鎖(讀鎖、寫鎖、行鎖等),當其餘線程想要訪問數據時,都須要阻塞掛起。能夠依靠數據庫實現,如行鎖、讀鎖和寫鎖等,都是在操做以前加鎖,在Java中,synchronized的思想也是悲觀鎖。屬於重量級鎖,會阻塞,會等待併發
若是想在不一樣的jvm中保證數據同步,使用分佈式鎖技術。有數據庫實現、緩存redis實現、Zookeeper分佈式鎖
所以咱們要慎重使用自旋鎖,自旋鎖只有在內核可搶佔式或SMP的狀況下才真正須要,在單CPU且不可搶佔式的內核下,自旋鎖的操做爲空操做。自旋鎖適用於鎖使用者保持鎖時間比較短的狀況下。
至於自旋鎖就主要用在臨界區持鎖時間很是短且CPU資源不緊張的狀況下,自旋鎖通常用於多核的服務器。
非公平鎖:在等待鎖的過程當中,若是有人以新的線程妄圖獲取鎖,都是有很大概率直接獲取到鎖的。白話文:公平鎖是先到先得,按序進行,非公平鎖就是不排隊直接拿,失敗再說。
Compare and Swap,即比較再交換。jdk5增長了併發包java.util.concurrent.*,其下面的類使用CAS算法實現了區別於synchronouse同步鎖的一種樂觀鎖。JDK 5以前Java語言是靠synchronized關鍵字保證同步的,這是一種獨佔鎖,也是是悲觀鎖。
與鎖相比,使用比較交換(下文簡稱CAS)會使程序看起來更加複雜一些。但因爲其非阻塞性,它對死鎖問題天生免疫,而且,線程間的相互影響也遠遠比基於鎖的方式要小。更爲重要的是,使用無鎖的方式徹底沒有鎖競爭帶來的系統開銷,也沒有線程間頻繁調度帶來的開銷,所以,它要比基於鎖的方式擁有更優越的性能。
java.util.concurrent.atomic包:原子類的小工具包,支持在單個變量上解除鎖的線程安全編程原子變量類至關於一種泛化的 volatile 變量,可以支持原子的和有條件的讀-改-寫操做。AtomicInteger 表示一個int類型的值,並提供了 get 和 set 方法,這些 Volatile 類型的int變量在讀取和寫入上有着相同的內存語義。它還提供了一個原子的 compareAndSet 方法(若是該方法成功執行,那麼將實現與讀取/寫入一個 volatile 變量相同的內存效果),以及原子的添加、遞增和遞減等方法。AtomicInteger 表面上很是像一個擴展的 Counter 類,但在發生競爭的狀況下能提供更高的可伸縮性,由於它直接利用了硬件對併發的支持。
若是同一個變量要被多個線程訪問,則可使用該包中的類 AtomicBoolean AtomicInteger AtomicLong AtomicReference