【多執行緒與高併發3】常用鎖例項

和樹不困發表於2020-12-26

各式鎖的實際應用





樂觀鎖 cas(要改的物件,期望的值,要給的值)無鎖操作,其實是一個樂觀鎖…cas本身可以看成是一個鎖


  • automic : 一種使用 cas 實現的原子性操作




原子操作的簡單方法:


函式效果備註
AtomicInteger a = new AtomicInteger(0);int a = 0;建立物件a並且賦初值為0;
a.incrementAndGet( );i++;對原值+1後返回;
a.getAndIncrement( );++i;對原值返回後+1;
a.addAndGet(i);a+=i;返回a+i;
a.getAndAdd(i);a+=i;返回原值之後給a+i;

線上程很多的情況下:LongAdder(分段鎖:線上程多的時候有優勢) > Atomic > synchronized。



Synchronized 的可重入性:


//可重入:
synchronized void m1(){
for(int i = 1;i<10;i++){
 try{
 	TimeUtil.SECONDS.sleep(1);// 睡一秒
	}catch(InterruptedException e){
 	e.printStackTrace();
	}
 sout(i);
}

}

synchronized void m2(){sout("m2...");}

public static void main(String[] args){
T01_ReentrantLock1 r1 = new T01_ReentrantLock1();
new Thread(r1::m1).start();
try{
 TimeUtil.SECONDS.sleep(1);// 睡一秒
}catch(InterruptedException e){
 e.printStackTrace();
}
new Thread(r1::m2).start();
}

輸出結果:0 1 23 4 5 6 7 8 9 m2…

程式碼修改:synchronized

//可重入:
synchronized void m1(){
  for(int i = 1;i<10;i++){
    try{
    	TimeUtil.SECONDS.sleep(1);// 睡一秒
    }catch(InterruptedException e){
      e.printStackTrace();
    }
    sout(i);
    if(i == 2){
    	new Thread(r1::m2).start();
    }
  }
}

synchronized void m2(){sout("m2...");}

public static void main(String[] args){
  T01_ReentrantLock1 r1 = new T01_ReentrantLock1();
  new Thread(r1::m1).start();
  try{
    TimeUtil.SECONDS.sleep(1);// 睡一秒
  }catch(InterruptedException e){
    e.printStackTrace();
  }

}

輸出結果:0 1 2 m2 … 3 4 5 6 7 8 9




lock():替代 synchronized 的方法;


Lock lock = new ReentrantLock();
  • 特點:

    • 需要手動上鎖 lock.lock( );
    • 需要手動解鎖lock.unlock( );
    • 防止程式出錯而導致死鎖,需要try{ …… }catch( ){ …… }

  • 優點:

    • 可以使用tryLock()嘗試上鎖;

    • synchronized遇到鎖之後只能等待,而tryLock()可以自定義等待時間;

    • locked = lock.tryLock(SECONDS(時間長度),TimeUtil.SECONDS(時間格式:秒));
      

  • 常用方法:

方法引數用法
.lock( );null鎖定
.unlock( );null釋放
.tryLock(n,TimeUtil.SECONDS);時間長度
時間單位
等待引數時間過程中:
如果當前程式釋放了,則鎖定;
不釋放則不鎖定;
.lockInterruptibly( );null;可以相應被打斷的鎖;
.interrupt( );Null;打斷這個鎖;




公平鎖

ReetrantLock lock = new ReentrantLock( true );


  • 概念:
    • 當執行佇列中有執行緒正在排隊的時候:
      • 公平鎖:繼續等待,排隊執行;
      • 不公平鎖:不等待,直接搶,有可能搶到第一個執行;
  • 建立方式:
    • 在建立鎖的時候加個 true 建立出來的就是公平鎖;
public class T05_ReentrantLock extends Thread(){
	private stratic ReentrantLock lock = new ReentrantLock(true);
  public void run(){
    for(int i = 0;i<100;i++){
      lock.lock();
      try{
        Sout(Thread.currentThread().getName()+"獲得鎖");
      }finally{
        lock.unlock();
      }
    }
  }
}




一個倒數計時的門栓 CountDownLatch


CountDownLatch latch = CountDownLatch( threads.length ); //建立一個length長度的門栓

.await() 阻塞

原join() 當前執行緒結束自動往前走

.countDown() 原子性–





柵欄工具 CyclicBarrier

迴圈柵欄工具


// 一個引數:不到20的時候,等待,到了20個,這20個發車,再來的繼續等待
CyclicBarrier barrier = new CyclicBarrier(20);
// 兩個引數:
CyclicBarrier barrier = new CyclicBarrier(20,run);
run(){ Sout("滿員,發車!"); }
//lambdo 表示式
CyclicBarrier barrier = new CyclicBarrier(20()->Sout("滿員,發車!"));




同步進行的 Phaser

按照不同的階段對執行緒進行劃分。


  • 使用場景:

    • 遺傳演算法
    • 現實生活一步一步執行的場景(如:婚禮)
    • 像是一個一個柵欄一樣

  • 使用方法:

    • 自定義一個類,繼承 Phaser類;

      static class MarrigePhaser extends Phaser

    • 重寫onAdvance方法;(柵欄被推倒的時候自動呼叫)

      protected boolean onAdvance(int phase,int registeredParties)


  • 方法:

    phaser.arriveAndAwaitAdvance();	//執行結束,開始等待;
    phaser.arriveAndDeregister();	//執行結束,不進入下一階段;
    




讀寫鎖

程式中的讀寫鎖(一種排他鎖、共享鎖)


  • 概念

    • A程式在讀取ABCD的時候,B程式也來讀取ABCD,同時發現A程式在讀取,則讀取成功;
    • A程式在讀取ABCD的時候,B程式來修改ABCD,同時發現A程式在讀取,若此時更改ABCD的內容,則A程式讀取會出問題,所以修改失敗;
    • **總結:**兩個都是讀取的程式可以同時進行,當有 讀 程式在進行時,無法進行 程式,寫同理;
  • 作用

    • 避免 / 減少 髒資料
static ReadWriteLoak readWriteLock = new ReentrantReadWriteLock();
//在 ReentrantReadWriteLock 中 分出一個 `readLock`一個`writeLock`
static Lock readLock = readWriteLock.readLock();
static Lock writeLock = readWriteLock.writeLock();

public static void read(Lock lock){
    try{
        lock.lock();
        Thread.sleep(1000);
        Sout("read over!");
        // 模擬讀取過程
    }catch(InterruptedException e){
        e.peintStackTrace();
    }finally{
        lock.unlock();
    }
}

public static void write(Lock lock,int a){
    try{
        lock.lock();
        Thread.sleep(1000);
        Sout("write "+ a +"over!");
        // 模擬讀取過程
    }catch(InterruptedException e){
        e.peintStackTrace();
    }finally{
        lock.unlock();
    }
}
    
public static void main(String[] args){
    
    Runnable readR = ()->read(lock);    
    //Runnable readR = ()->read(readLock);
    
    Runnable write = ()->write(lock,new Random().nextInt());
    
    for (int i=0;i<18;i++)new Thread(readR ).start();
    for (int i=0;i<2 ;i++)new Thread(writeR).start();
	}
}

// 如果使用 ReentrantLock的話,以上程式碼在執行的時候也需要等待一秒;

// 解決方法:將Main方法中的鎖換成Runnable readR = ()-> read(readLock);





Semaphore 一個有意思的執行緒池

Semaphore s = new Semaphore(x);x是幾則這個 < 執行緒池 > 就 允許幾個執行緒 同時執行。


public static void main(String[] args){
    Semaphore s = new Semaphore(1);
    //括號中數字為x時,允許x個執行緒同時執行
    
    // T1 Running
    new Thread(()->{
        try{
            s.acquire();
            // 進來一個程式 1 變成 0 ,別的執行緒不能執行
            Sout("T1 Running");
            Thread.sleep(200);
            Sout("T1 Running");
        }catch(InterruptedException e){
            e.printStackTrace();
        }finally{
            s.release();
            // 離開一個程式 0 變成 1 ,別的執行緒可以執行
        }
    });
    
        // T2 Running
        new Thread(()->{
        try{
            s.acquire();
            // 進來一個程式 1 變成 0 ,別的執行緒不能執行
            Sout("T2 Running");
            Thread.sleep(200);
            Sout("T2 Running");
        }catch(InterruptedException e){
            e.printStackTrace();
        }finally{
            s.release();
            // 離開一個程式 0 變成 1 ,別的執行緒可以執行
        }
    });
}

如果x==1則執行結果是T1 T1 T2 T2,否則可能是T1 T2 T1 T2




Exchanger 用於 < ! 兩個 ! > 執行緒交換資料的方法

使用場景:雙人遊戲中兩人交換裝備!執行一次就失效,可以迴圈等待下一次;


public static void main(String[] args){
    // T1
    new Thread(()->{
        String s = "T1";
        try{
            s = sxchanger.exchange(s);
        }cathc(InterruptedException e){
            e.printStackTrace();
        }
        Sout(Thread.currentThread().getName()+""+s);
    },"t1").start();
    
    // T2
    new Thread(()->{
        String s = "T2";
        try{
            s = sxchanger.exchange(s);
        }cathc(InterruptedException e){
            e.printStackTrace();
        }
        Sout(Thread.currentThread().getName()+""+s);
    },"t2").start();
}

執行緒中有兩個變數,分別是ss(區域性變數),兩個執行緒同時執行,最後交換T1T2的值;





分散式鎖


只是某個型別的鎖,將來補充概念。





總結 :


  • 無論何種情況,優先考慮使用synchronized
  • 讀寫的時候讀寫鎖可以大大提升效率
  • locksynchronized要靈活很多,但是需要自己加鎖並釋放鎖,所以不是很方便

相關文章