鎖分類與原理
鎖的分類
鎖的分類及基本概念
樂觀鎖:假定沒有衝突,在修改數據如果發現數據和之前獲取的不一致,則讀取最新的數據,再次嘗試修改。
悲觀鎖:假定會發生衝突,同步所有的數據的相關操作,從讀數據就開始上鎖。
獨佔鎖:給資源加上寫鎖,當前線程可以修改資源,其它線程不能再加鎖(單寫)
共享鎖:給資源加上讀鎖後只能讀不能改,不能加寫鎖(多讀)
重入鎖:同時加兩次鎖,不會出現死鎖(再次拿到鎖)
可重入鎖:同時加兩次鎖,會出現死鎖(阻塞)
公平鎖:搶到鎖的順序和搶鎖順序相同則爲公平鎖
非公平鎖:搶到鎖的順序和搶鎖順序無關。
synchronized 的特性:
1. 用於實例方法,靜態方法時,隱式指定鎖對象
2. 用於代碼塊時,顯示指定鎖對象
3. 鎖的作用域:對象鎖,類鎖,分佈式鎖
4. 是可重入鎖,也是獨享鎖和悲觀鎖
/**
* 測試 synchronized 的可重入性
*/
public class RecursiveTest {
public static void main(String[] args) throws InterruptedException {
recursive();
}
public static synchronized void recursive() throws InterruptedException {
System.out.println("here i am ...");
Thread.sleep(1000L);
recursive();
}
}
Java對象在內存中的佈局:
示例代碼:
public class Demo {
public static void main(String[] args) {
int a = 1;
SalesOrder order = new SalesOrder();
order.cust = new Customer();
}
}
class SalesOrder {
Long id = 1001L;
int age = 50;
Customer cust;
public void discount() {
//do something
}
}
class Customer {
String name = "Julie";
int age = 18;
boolean gender = false;
}
對象頭部
輕量級鎖:
當沒有成功的線程自旋達到一定次數時,鎖升級爲重量級鎖:
鎖的升級過程:
根據上述,鎖有四個狀態,分別爲:
- 未鎖定
- 偏向鎖
- 輕量級鎖
- 重量級鎖
下面爲鎖之間升級的狀態圖:
鎖的本質:
爭搶能夠訪問資源的一種權限。
Lock接口API:
public interface Lock {
//獲取鎖(不死不休)
void lock();
//嘗試獲取鎖(淺嘗輒止)
boolean tryLock();
//獲取鎖(過時不候)
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//獲取鎖(任人擺佈)
void lockInterruptibly() throws InterruptedException;
//釋放鎖
void unlock();
//返回一個新Condition綁定到該實例Lock實例。
Condition newCondition();
}
Lock 包類之間的關係:
lock() 最常用
lockInterruptibly() 方法一般更昂貴,有的實現類中可能沒有實現lockInterruptibly() ,只有真的需要中斷時才使用,使用之前看看實現該方法的描述。
Condition
Object 中的wait(),notify(),notifyAll() 只能和synchronized配合使用,可以喚醒一個或者全部(單個等待集);而Condition需要與Lock配合使用,提供多個等待集合,更精確的控制。以下是使用Condition的示例:
public class Demo {
private static Lock lock = new ReentrantLock();
private static Condition condition = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
lock.lock();
try {
System.out.println("子線程調用condition.await()...");
//子線程調用await()後,會自動釋放鎖以備主線程調用。
condition.await();
System.out.println("子線程condition.await() 後面的代碼...");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
});
thread.start();
Thread.sleep(5000L);
lock.lock();
condition.signalAll();
lock.unlock();
}
}
Condition中使用時應避免出現死鎖場景:在未調用await()之前調用了signal() 或signalAll() :
public class Demo {
private static Lock lock = new ReentrantLock();
private static Condition condition = lock.newCondition();
public static void main(String[] args) {
Thread thread = new Thread(() -> {
try {
Thread.sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("子線程調用lock()");
lock.lock();
try {
System.out.println("獲得鎖,調用condition.await()\n");
condition.await();
System.out.println("喚醒了...\n");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
});
thread.start();
lock.lock();
condition.signalAll();
System.out.println("主線程調用signalAll()");
lock.unlock();
}
}
synchronized 與Lock 的區別:
synchronized
優點:
1. 使用簡單,語義清晰。
2. 由JVM提供,提供了多種優化方案(鎖粗化,鎖消除,偏向鎖,輕量級鎖)
3. 鎖的釋放由JVM來完成,不用人工干預,也降低死鎖的可能性。
缺點:
4. 無法實現一些鎖的高級功能,如公平鎖,中斷鎖,讀寫鎖,共享鎖等。
Lock
優點: 所有synchronized的缺點,可以實現更多的功能 。
缺點:需要釋放鎖,使用不當可能造成死鎖。
互斥鎖的原理:
步驟說明:
- t1,t2,t3,t4分別都排隊搶鎖
- 假如t1 線程搶到鎖,這時將count用CAS(0,1)改爲1,將owner 改爲t1
- t2,t3,t4 未搶到鎖,依次按順序放入等待隊列waiters 中
- t1 用完後釋放鎖,這時count = 0,owner = null;喚醒等待隊列中的第一個t2;
- t2 拿到鎖後將owner改爲t2,count 進行CAS(0,1) 修改。
1.讀寫鎖
讀寫鎖:在讀與寫的操作中,讀可以併發讀,寫只能用一個線程寫。但是讀鎖與寫鎖之間是互斥的。
ReadWriteLock:
維護一對關聯鎖,一個只用於讀操作,一個只用於寫操作;讀鎖可以由多個讀線程同時持有,寫鎖是排他鎖。同一時間,讀鎖和寫鎖不能被不同的線程持有。
讀寫鎖改造HashMap:
public class Demo {
private final Map<String, Object> map = new HashMap<>();
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final Lock readLock = readWriteLock.readLock();
private final Lock writeLock = readWriteLock.writeLock();
public Object get(String key) {
readLock.lock();
try {
return map.get(key);
} finally {
readLock.unlock();
}
}
public Object[] allKeys() {
readLock.lock();
try {
return map.keySet().toArray();
} finally {
readLock.unlock();
}
}
public Object put(String key, Object value) {
writeLock.lock();
try {
return map.put(key, value);
} finally {
writeLock.unlock();
}
}
public void clear() {
writeLock.lock();
try {
map.clear();
} finally {
writeLock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(5);
Demo demo= new Demo ();
for (int i = 0; i < 100000; i++) {
int n = i;
new Thread(() -> {
if (n % 1 == 0)
demo.put(n + "", "n % 1 == 0");
else
demo.put(n + "", n + "_123");
countDownLatch.countDown();
}).start();
}
countDownLatch.await();
System.out.println(demo.get("39333"));
}
}