鎖做爲併發共享數據,保證一致性的工具,在JAVA平臺有多種實現(如 synchronized(重量級) 和 ReentrantLock(輕量級)等等 ) 。這些已經寫好提供的鎖爲咱們開發提供了便利。java
重入鎖,也叫作遞歸鎖,指的是同一線程 外層函數得到鎖以後 ,內層遞歸函數仍然有獲取該鎖的代碼,但不受影響。mysql
在JAVA環境下 ReentrantLock 和synchronized 都是 可重入鎖算法
synchronized:sql
public class Test implements Runnable {
public synchronized void get() {
System.out.println("name:" + Thread.currentThread().getName() + " get();");
}
public synchronized void set() {
System.out.println("name:" + Thread.currentThread().getName() + " set();");
get();
}
@Override
public void run() {
set();
}
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();
}
}複製代碼
ReentrantLock:數據庫
public class Test02 extends Thread {
ReentrantLock lock = new ReentrantLock();
public void get() {
try{
lock.lock();
System.out.println(Thread.currentThread().getId());
}catch (Exception e){
}finally {
lock.unlock();
}
}
public void set() {
try{
lock.lock();
System.out.println(Thread.currentThread().getId());
get();
}catch (Exception e){
}finally {
lock.unlock();
}
}
@Override
public void run() {
set();
}
public static void main(String[] args) {
Test ss = new Test();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
}
}複製代碼
正常狀況下在函數自行完畢後會釋放鎖,以上兩種方式均在已得到鎖的函數中從新調用了須要鎖的函數,若是沒有可重入性,那麼調用新的須要鎖的函數時,將會形成死鎖。可重用鎖在遞歸調用中很是重要。編程
程序中涉及到對一些共享資源的讀和寫操做,且寫操做沒有讀操做那麼頻繁。在沒有寫操做的時候,兩個線程同時讀一個資源沒有任何問題,因此應該容許多個線程能在同時讀取共享資源。安全
可是若是有一個線程想去寫這些共享資源,就不該該再有其它線程對該資源進行讀或寫(也就是說:讀-讀能共存,讀-寫不能共存,寫-寫不能共存)。數據結構
這就須要一個讀/寫鎖來解決這個問題。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) {
try {
r.lock();
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) {
try {
w.lock();
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() {
try {
w.lock();
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操做實現。ide
version方式:通常是在數據表中加上一個數據版本號version字段,表示數據被修改的次數,當數據被修改時,version值會加一。當線程A要更新數據值時,在讀取數據的同時也會讀取version值,在提交更新時,若剛纔讀取到的version值爲當前數據庫中的version值相等時才更新,不然重試更新操做,直到更新成功。
核心SQL語句:
update table set x=x+1, version=version+1 where id=#{id} and version=#{version};
CAS操做方式:即compare and swap 或者 compare and set,涉及到三個操做數,數據所在的內存值,預期值,新值。當須要更新時,判斷當前內存值與以前取到的值是否相等,若相等,則用新值更新,若失敗則重試,通常狀況下是一個自旋操做,即不斷的重試。
老是假設最壞的狀況,每次取數據時都認爲其餘線程會修改,因此都會加鎖(讀鎖、寫鎖、行鎖等),當其餘線程想要訪問數據時,都須要阻塞掛起。能夠依靠數據庫實現,如行鎖、讀鎖和寫鎖等,都是在操做以前加鎖,在Java中,synchronized的思想也是悲觀鎖。
java.util.concurrent.atomic包:原子類的小工具包,支持在單個變量上解除鎖的線程安全編程
原子變量類至關於一種泛化的 volatile 變量,可以支持原子的和有條件的讀-改-寫操做。
AtomicInteger 表示一個int類型的值,並提供了 get 和 set 方法,這些 Volatile 類型的int變量在讀取和寫入上有着相同的內存語義。它還提供了一個原子的 compareAndSet 方法(若是該方法成功執行,那麼將實現與讀取/寫入一個 volatile 變量相同的內存效果),以及原子的添加、遞增和遞減等方法。AtomicInteger 表面上很是像一個擴展的 Counter 類,但在發生競爭的狀況下能提供更高的可伸縮性,由於它直接利用了硬件對併發的支持。
爲何會有原子類CAS:Compare and Swap,即比較再交換。
jdk5增長了併發包java.util.concurrent.*,其下面的類使用CAS算法實現了區別於synchronouse同步鎖的一種樂觀鎖。相似於mysql中的version字段的樂觀鎖
若是同一個變量要被多個線程訪問,則可使用該包中的類
CAS:Compare and Swap,即比較再交換。jdk5增長了併發包java.util.concurrent.*,其下面的類使用CAS算法實現了區別於synchronouse同步鎖的一種樂觀鎖。JDK 5以前Java語言是靠synchronized關鍵字保證同步的,這是一種獨佔鎖,也是是悲觀鎖。
CAS存在一個很明顯的問題,即ABA問題。
問題:若是變量V初次讀取的時候是A,而且在準備賦值的時候檢查到它仍然是A,那能說明它的值沒有被其餘線程修改過了嗎?
若是在這段期間曾經被改爲B,而後又改回A,那CAS操做就會誤認爲它歷來沒有被修改過。針對這種狀況,java併發包中提供了一個帶有標記的原子引用類AtomicStampedReference,它能夠經過控制變量值的版原本保證CAS的正確性。
我的博客 蝸牛