可重入鎖
我們來看什麼是可重入鎖
可重入鎖的核心意思 是同一個線程在外層方法獲取鎖的時候 在進入內層方法會自動獲取鎖
這裏我們分別舉synchronized 和 ReentrantLock的例子
synchronized是可重入鎖
package com.robot;
class Phone{
//外層方法
public synchronized void sendMes() {
System.out.println(Thread.currentThread().getName()+"\t sendMes");
call();//進入內層方法會自動獲取鎖
}
//內層方法也別synchronized修飾
public synchronized void call() {
System.out.println(Thread.currentThread().getName()+"\t call");
}
}
public class Demo1 {
public static void main(String[] args) {
Phone phone=new Phone();
new Thread(()->{
phone.sendMes();
},"t1").start();
}
}
//控制檯結果
t1 sendMes
t1 call
ReentrantLock是可重入鎖
package com.zyk;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Phone{
//兩個方法持有同一個lock
Lock lock=new ReentrantLock();//默認是非公平鎖
//外層方法
public void sendMes(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t sendMes");
//外層方法調用內層方法 自動獲取鎖
call();
}finally {
lock.unlock();
}
}
//內層方法被lock鎖鎖住
public void call(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"\t call");
}finally {
lock.unlock();
}
}
}
public class ReentrantLockDemo {
public static void main(String[] args) {
Phone phone=new Phone();
new Thread(()->{
phone.sendMes();
},"A").start();
}
}
結果
A sendMes
A call
自旋鎖
是指嘗試獲取鎖的線程不會立即阻塞 而是採用循環的方式去嘗試獲取鎖
我們來模擬一種自旋的情況
package com.zyk;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
public class SpinLockDemo {
//原子引用 引用的是線程
AtomicReference<Thread> atomicReference=new AtomicReference<>();
public void myLock(){
Thread thread=Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"進來了");
while(!atomicReference.compareAndSet(null,thread)){
}
}
public void myUnlock(){
Thread thread=Thread.currentThread();
atomicReference.compareAndSet(thread,null);
System.out.println(Thread.currentThread().getName()+"離開了");
}
public static void main(String[] args) {
SpinLockDemo spinLockDemo=new SpinLockDemo();
new Thread(()->{
spinLockDemo.myLock();
try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
spinLockDemo.myUnlock();
},"AAA").start();
//確保AAA線程先拿到鎖
try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
new Thread(()->{
spinLockDemo.myLock();
spinLockDemo.myUnlock();
},"BBB").start();
}
}
結果
AAA進來了
BBB進來了
AAA離開了
BBB離開了
這塊我們具體分析一下這個上述的過程
AAA首先獲得了鎖 —》等待3秒之後 BBB進入這個myLock方法 在while循環這裏 CAS比較不滿足條件 (因爲此時真實值不是null)就一直在這裏執行自旋過程—》再過7秒之後 AAA釋放鎖—》BBB拿到鎖 結束自旋–》
注意
BBB進來了之後 並沒有拿到鎖 在這7秒鐘之內 會一直自旋嘗試獲取
如果代碼是這樣
while(!atomicReference.compareAndSet(null,thread)){
System.out.println(Thread.currentThread().getName()+"在這裏自旋");
}
就會發現 在BBB3秒鐘這個時刻進來之後 到第10秒的這7秒內 控制檯會打印滿屏的在這裏自旋
讀寫鎖
獨佔鎖 (讀鎖) 該鎖一次只能被一個線程鎖持有 ReentrantLock和synchronized都是獨佔鎖
共享鎖 (寫鎖) 該鎖可以被多個線程鎖持有
我們分析一個具體生活案例 在機場的顯示屏上顯示着各個航班的信息 那麼 我們底下圍着一圈人去看這個信息 這就好比共享鎖 (肯定不能一個個人去排隊看)這樣就提高了我們的併發性 但是在某個航班延誤的時候 只能有機場的工作人員去進行修改 這就好比寫鎖 如果這時候每個人每個線程都去寫 呢這就直接亂了套了
我們先看一個沒有加鎖的錯誤示例
package com.zyk;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
class MyCache{
private volatile Map<String,Object> map=new HashMap<>();
public void put(String key,Object value){
System.out.println(Thread.currentThread().getName()+"正在寫入"+key);
try { TimeUnit.MICROSECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
map.put(key, value);
System.out.println(Thread.currentThread().getName()+"寫完了");
}
public void get(String key){
System.out.println(Thread.currentThread().getName()+"正在讀取");
try { TimeUnit.MICROSECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
Object result=map.get(key);
System.out.println(Thread.currentThread().getName()+"讀完了"+result);
}
}
public class ReadAndWriteLockDemo {
public static void main(String[] args) {
MyCache myCache=new MyCache();
for (int i = 1; i <=5 ; i++) {
final int temp=i;
new Thread(()->{
myCache.put(temp+"",temp+"");
}).start();
}
for (int i = 1; i <=5 ; i++) {
final int temp=i;
new Thread(()->{
myCache.get(temp+"");
}).start();
}
}
}
這個時候我們如果使用了傳統lock鎖 呢麼雖然數據安全性得到保證 但是併發的性能就會大大的下降 這是juc爲我們提供了一種解決方式ReentrantReadWriteLock
我們這裏在寫的時候將會使用到 reentrantReadWriteLock.writeLock().lock();這個方法
我麼這裏在讀的時候將會使用到reentrantReadWriteLock.readLock().lock();這個方法
package com.zyk;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class MyCache {
//寫入類似緩存的東西用volatile修飾 保證可見性 第一時間拿到數據
private volatile Map<String, Object> map = new HashMap<>();
private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
public void put(String key, Object value) {
reentrantReadWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "開始寫" +key);
try { TimeUnit.MICROSECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "寫完了");
} catch (Exception e) {
e.printStackTrace();
} finally {
reentrantReadWriteLock.writeLock().unlock();
}
}
public void get(String key) {
reentrantReadWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "開始讀");
try { TimeUnit.MICROSECONDS.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
Object result = map.get(key);
System.out.println(Thread.currentThread().getName() + "讀結束" + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
reentrantReadWriteLock.readLock().unlock();
}
}
}
public class ReadAndWriteLockDemo {
public static void main(String[] args) {
MyCache myCache=new MyCache();
for (int i = 1; i <=5 ; i++) {
final int temp=i;
new Thread(()->{
myCache.put(temp+"",temp+"");
}).start();
}
for (int i = 1; i <=5 ; i++) {
final int temp=i;
new Thread(()->{
myCache.get(temp+"");
}).start();
}
}
}
這裏的結果就既保證了數據一致性 又保證了併發性 達到了我們滿意的效果
nice 收工了 !!
晚安 好夢 希望大家心想事成