可重入鎖+自旋鎖+讀寫鎖

可重入鎖

我們來看什麼是可重入鎖

可重入鎖的核心意思 是同一個線程在外層方法獲取鎖的時候 在進入內層方法會自動獲取鎖

這裏我們分別舉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 收工了 !!

晚安 好夢 希望大家心想事成


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章