浪費了一個週末啥都沒學 QAQjava
日期:2019年7月2日23:05:51數據庫
方法 | 描述 |
lock | 獲取鎖的方法,若鎖被其餘線程獲取,則等待(阻塞)緩存 |
lockinterruptibly | 在鎖的獲取過程當中能夠中斷當前線程安全 |
tryLockbash |
嘗試非阻塞地獲取鎖,當即返回併發 |
unlock | 釋放鎖ide |
Tips:post
根據Lock接口的源碼註釋,Lock接口的實現, 具有和同步關鍵字一樣的內存語義。性能
public class ReentrantDemo1 {
private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
lock.lock(); // block until condition holds
try {
System.out.println("第一次獲取鎖");
System.out.println("當前線程獲取鎖的次數" + lock.getHoldCount());
lock.lock();
System.out.println("第二次獲取鎖了");
System.out.println("當前線程獲取鎖的次數" + lock.getHoldCount());
} finally {
lock.unlock();
lock.unlock();
}
System.out.println("當前線程獲取鎖的次數" + lock.getHoldCount());
// 若是不釋放,此時其餘線程是拿不到鎖的
new Thread(() -> {
System.out.println(Thread.currentThread() + " 指望搶到鎖");
lock.lock();
System.out.println(Thread.currentThread() + " 線程拿到了鎖");
}).start();
}
}
複製代碼
中斷只是標記線程應該被中斷,但不是立刻中止ui
// 可響應中斷
public class LockInterruptiblyDemo1 {
private Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
LockInterruptiblyDemo1 demo1 = new LockInterruptiblyDemo1();
Runnable runnable = new Runnable() {
@Override
public void run() {
try {
demo1.test(Thread.currentThread());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread thread1 = new Thread(runnable);
Thread thread2 = new Thread(runnable);
thread1.start();
Thread.sleep(500); // 等待0.5秒,讓thread1先執行
thread2.start();
Thread.sleep(2000); // 兩秒後,中斷thread2
thread2.interrupt();
}
public void test(Thread thread) throws InterruptedException {
System.out.println(Thread.currentThread().getName() + ", 想獲取鎖");
lock.lockInterruptibly(); //注意,若是須要正確中斷等待鎖的線程,必須將獲取鎖放在外面,而後將InterruptedException拋出
try {
System.out.println(thread.getName() + "獲得了鎖");
Thread.sleep(10000); // 搶到鎖,10秒不釋放
} finally {
System.out.println(Thread.currentThread().getName() + "執行finally");
lock.unlock();
System.out.println(thread.getName() + "釋放了鎖");
}
}
}複製代碼
線程安全問題:變量沒有知足 可見性 和 原子性。
讀鎖 -> 共享鎖
寫鎖 -> 獨享鎖
能夠多個線程同時讀
當沒有讀寫鎖時,大量請求在沒有命中緩存的狀況下,所有打到 db 上。
有可能出現數據不一致的狀況。
// 緩存示例
public class CacheDataDemo {
// 建立一個map用於緩存
private Map<String, Object> map = new HashMap<>();
private static ReadWriteLock rwl = new ReentrantReadWriteLock();
public static void main(String[] args) {
// 1 讀取緩存裏面的數據
// cache.query()
// 2 若是換成沒數據,則取數據庫裏面查詢 database.query()
// 3 查詢完成以後,數據塞到塞到緩存裏面 cache.put(data)
}
public Object get(String id) {
Object value = null;
// 首先開啓讀鎖,從緩存中去取
rwl.readLock().lock();
try {
if (map.get(id) == null) {
// TODO database.query(); 所有查詢數據庫 ,緩存雪崩
// 必須釋放讀鎖
rwl.readLock().unlock();
// 若是緩存中沒有釋放讀鎖,上寫鎖。若是不加鎖,全部請求所有去查詢數據庫,就崩潰了
rwl.writeLock().lock(); // 全部線程在此處等待 1000 1 999 (在同步代碼裏面再次檢查是否緩存)
try {
// 雙重檢查,防止已經有線程改變了當前的值,從而出現重複處理的狀況
if (map.get(id) == null) {
// TODO value = ...若是緩存沒有,就去數據庫裏面讀取
}
rwl.readLock().lock(); // 加讀鎖降級寫鎖,這樣就不會有其餘線程可以改這個值,保證了數據一致性
} finally {
rwl.writeLock().unlock(); // 釋放寫鎖@
}
}
/* 在這裏又進行了一系列操做,在操做過程當中,有可能數據改變致使緩存內容改變 此時,要在寫鎖中加入讀鎖,防止相似於 幻讀,髒讀 等的產生 */
} finally {
rwl.readLock().unlock();
}
return value;
}
}
複製代碼
/** * 本身手動實現的一個 reentrantLock * */
public class GzyLock implements Lock {
//須要 CAS 自旋的方式去實現
//模仿 monitor obj 的重量級鎖 有一個 owner
private static AtomicReference<Thread> owner = new AtomicReference<>();
private static LinkedBlockingDeque<Thread> waiters = new LinkedBlockingDeque<>();
@Override
public void lock() {
boolean addQueue = true;
while (!tryLock()){
//第一次進來會放到queue
if(addQueue) {
//若是沒有獲取到鎖,就先存到 queue
waiters.offer(Thread.currentThread());
addQueue = false;
}else {
//park 等待被喚醒。這裏不能用 wait/notify 由於須要在同步代碼塊用
LockSupport.park();
}
//喚醒後嘗試爭搶,沒搶到繼續等
}
//搶到鎖,移除掉
waiters.remove(Thread.currentThread());
}
@Override
public boolean tryLock() {
//適用當前線程嘗試加鎖
return owner.compareAndSet(null, Thread.currentThread());
}
@Override
public void unlock() {
//unlock 的時候,要喚醒等待線程
//若是釋放鎖成功了,纔會喚起
//這裏用 if 是由於,必定不會出現 循環
if (owner.compareAndSet(Thread.currentThread(), null)) {
//一次喚醒全部在等待隊列的
for (Thread waiter : waiters) {
//喚起等待隊列的線程
LockSupport.unpark(waiter);
}
}
}
public static void main(String[] args) throws InterruptedException {
Adder adder = new Adder();
for (int j = 0; j < 10; j++) {
Thread addThread = new Thread(() -> {
for (int i = 0; i < 1000; i++) {
adder.add();
}
});
addThread.start();;
}
Thread.sleep(1000L);
System.out.println(adder.i);
}
static class Adder{
Lock lock = new GzyLock();
int i = 0;
public void add(){
lock.lock();
try {
i++;
}finally {
lock.unlock();
}
}
}
}
複製代碼
結合源碼的初步理解,具體的定義:抽象隊列同步器
複製代碼
只有鎖和鎖池(waiters),定義了 鎖(線程) 的獲取和釋放後的處理邏輯。
抽象了 獲取和釋放 鎖(資源)的方法,須要根據具體的業務場景去實現。例如:同步鎖、非同步鎖;獨享鎖,共享鎖。
等待/喚醒邏輯,都由 AQS 去實現了。由於不管什麼樣的鎖,都須要去等待。
源碼來一波