天天看點

12.鎖雜談

文章目錄

  • ​​部落格概述​​
  • ​​鎖​​
  • ​​重入鎖Reentrantlock​​
  • ​​鎖與等待/通知​​
  • ​​多Condition​​
  • ​​Lock和Condition的其他方法和用法​​
  • ​​讀寫鎖​​
  • ​​鎖的總結​​

部落格概述

在java多線程中,我們知道可以使用sync關鍵字實作線程間的同步互斥工作,那麼其實還有一個更優秀的機制去完成這個同步互斥工作,他就是lock對象,我主要介紹2種鎖,重入鎖和讀寫鎖。他們具有比sync關鍵字更強大的功能。父接口是lock接口。有三種鎖實作-讀鎖,寫鎖,重入鎖。關于segment的這個實作,沒有公開使用,隻是jdk内部使用的一個實作。

12.鎖雜談

重入鎖Reentrantlock

重入鎖,在需要進行同步的代碼部分上加上鎖定,但不要忘記最後一定要釋放鎖定,不然會造成鎖永遠無法釋放,其他線程永遠進不來的結果。對于這種特性考慮用try-catch-finally來做。之前可能是在方法是加sync關鍵字來實作方法的同步。現在用重入鎖實作更細粒度的鎖的控制。

import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class UseReentrantLock {
  
  private Lock lock = new ReentrantLock();
  
  public void method1(){
    try {
      lock.lock();
      System.out.println("目前線程:" + Thread.currentThread().getName() + "進入method1..");
      Thread.sleep(1000);
      System.out.println("目前線程:" + Thread.currentThread().getName() + "退出method1..");
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    } finally {
      
      lock.unlock();
    }
  }
  
  public void method2(){
    try {
      lock.lock();
      System.out.println("目前線程:" + Thread.currentThread().getName() + "進入method2..");
      Thread.sleep(2000);
      System.out.println("目前線程:" + Thread.currentThread().getName() + "退出method2..");
      Thread.sleep(1000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    } finally {
      
      lock.unlock();
    }
  }
  
  public static void main(String[] args) {

    final UseReentrantLock ur = new UseReentrantLock();
    Thread t1 = new Thread(new Runnable() {
      @Override
      public void run() {
        ur.method1();
        ur.method2();
      }
    }, "t1");

    t1.start();
    try {
      Thread.sleep(10);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    //System.out.println(ur.lock.getQueueLength());
  }
  
  
}      

類似sync關鍵字。但是更加靈活。 一個重入鎖,控制單一對象的兩個方法的通路,實作同步。雖然在兩個方法上加sycn關鍵字也能實作,jdk1.8之後,兩者(sync與重入鎖)性能差不多了。是以用起來不要糾結了。但是lock比sync關鍵字更靈活。

鎖與等待/通知

我們在使用sync的時候,如果多線程之間進行協作工作則需要Object的wait/notify方法進行配合工作。那麼同樣,我們使用Lock的時候,可以使用使用一個新的等待/通知的類,它就是condition。這個condition一定是針對某一把具體的鎖的,也就隻有在鎖的基礎上才會産生condition。

import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class UseCondition {

  private Lock lock = new ReentrantLock();
  private Condition condition = lock.newCondition();
  
  public void method1(){
    try {
      lock.lock();
      System.out.println("目前線程:" + Thread.currentThread().getName() + "進入等待狀态..");
      Thread.sleep(3000);
      System.out.println("目前線程:" + Thread.currentThread().getName() + "釋放鎖..");
      condition.await();  // Object wait
      System.out.println("目前線程:" + Thread.currentThread().getName() +"繼續執行...");
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
  
  public void method2(){
    try {
      lock.lock();
      System.out.println("目前線程:" + Thread.currentThread().getName() + "進入..");
      Thread.sleep(3000);
      System.out.println("目前線程:" + Thread.currentThread().getName() + "發出喚醒..");
      condition.signal();   //Object notify
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
  
  public static void main(String[] args) {
    
    final UseCondition uc = new UseCondition();
    Thread t1 = new Thread(new Runnable() {
      @Override
      public void run() {
        uc.method1();
      }
    }, "t1");
    Thread t2 = new Thread(new Runnable() {
      @Override
      public void run() {
        uc.method2();
      }
    }, "t2");
    t1.start();

    t2.start();
  }
  
  
  
}      

signal發信号喚醒,signal不釋放鎖。await釋放鎖。得等着發出信号的這塊被lock的代碼執行完了,等待的線程才會繼續運作。跟wait/notify的機制是一緻的。但是這種方式,更加靈活,靈活的原因是因為可以多condition。

多Condition

我們可以通過一個lock對象産生多個condition進行多線程的互動,非常的靈活。可以使得部分需要喚醒的線程喚醒,其他的線程則需要繼續等待通知。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class UseManyCondition {

  private ReentrantLock lock = new ReentrantLock();
  private Condition c1 = lock.newCondition();
  private Condition c2 = lock.newCondition();
  
  public void m1(){
    try {
      lock.lock();
      System.out.println("目前線程:" +Thread.currentThread().getName() + "進入方法m1等待..");
      c1.await();
      System.out.println("目前線程:" +Thread.currentThread().getName() + "方法m1繼續..");
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
  
  public void m2(){
    try {
      lock.lock();
      System.out.println("目前線程:" +Thread.currentThread().getName() + "進入方法m2等待..");
      c1.await();
      System.out.println("目前線程:" +Thread.currentThread().getName() + "方法m2繼續..");
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
  
  public void m3(){
    try {
      lock.lock();
      System.out.println("目前線程:" +Thread.currentThread().getName() + "進入方法m3等待..");
      c2.await();
      System.out.println("目前線程:" +Thread.currentThread().getName() + "方法m3繼續..");
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
  
  public void m4(){
    try {
      lock.lock();
      System.out.println("目前線程:" +Thread.currentThread().getName() + "喚醒..");
      c1.signalAll();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
  
  public void m5(){
    try {
      lock.lock();
      System.out.println("目前線程:" +Thread.currentThread().getName() + "喚醒..");
      c2.signal();
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
  
  public static void main(String[] args) {
    
    
    final UseManyCondition umc = new UseManyCondition();
    Thread t1 = new Thread(new Runnable() {
      @Override
      public void run() {
        umc.m1();
      }
    },"t1");
    Thread t2 = new Thread(new Runnable() {
      @Override
      public void run() {
        umc.m2();
      }
    },"t2");
    Thread t3 = new Thread(new Runnable() {
      @Override
      public void run() {
        umc.m3();
      }
    },"t3");
    Thread t4 = new Thread(new Runnable() {
      @Override
      public void run() {
        umc.m4();
      }
    },"t4");
    Thread t5 = new Thread(new Runnable() {
      @Override
      public void run() {
        umc.m5();
      }
    },"t5");
    
    t1.start(); // c1
    t2.start(); // c1
    t3.start(); // c2
    

    try {
      Thread.sleep(2000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }

    t4.start(); // c1
    try {
      Thread.sleep(2000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    t5.start(); // c2
    
  }
  
  
  
}      

運作結果如下:m123方法分别執行,然後分别釋放鎖。t4喚醒1和2。最後t5喚醒t3。

Connected to the target VM, address: '127.0.0.1:55183', transport: 'socket'
目前線程:t1進入方法m1等待..
目前線程:t2進入方法m2等待..
目前線程:t3進入方法m3等待..
目前線程:t4喚醒..
目前線程:t1方法m1繼續..
目前線程:t2方法m2繼續..
目前線程:t5喚醒..
目前線程:t3方法m3繼續..
Disconnected from the target VM, address: '127.0.0.1:55183', transport: 'socket'      

Lock和Condition的其他方法和用法

12.鎖雜談

公平鎖:先調用先上鎖。需要維護順序。

非公平:按CPU的配置設定來上鎖,不需要維護順序,性能要好。

鎖的嗅探:嘗試獲得鎖的過程。trylock方法就是嗅探。省時間,獲得不到,不急的話就先等着。

大當量的并發,并發任務有優先級。

import java.util.concurrent.locks.ReentrantLock;
/**
 * lock.getHoldCount()方法:隻能在目前調用線程内部使用,不能再其他線程中使用
 * 那麼我可以在m1方法裡去調用m2方法,同時m1方法和m2方法都持有lock鎖定即可 測試結果holdCount數遞增
 *
 */
public class TestHoldCount {

  //重入鎖
  private ReentrantLock lock = new ReentrantLock();
  
  public void m1(){
    try {
      lock.lock();
      System.out.println("進入m1方法,holdCount數為:" + lock.getHoldCount());
      
      //調用m2方法
      m2();
      
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
  
  public void m2(){
    try {
      lock.lock();
      System.out.println("進入m2方法,holdCount數為:" + lock.getHoldCount());
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      lock.unlock();
    }
  }
  
  
  public static void main(String[] args) {
    TestHoldCount thc = new TestHoldCount();
    thc.m1();
  }
}      

讀寫鎖

12.鎖雜談

寫多讀少:用重入鎖。讀多寫少,重人鎖。

口訣:讀讀共享,有寫互斥。

讀寫鎖不要直接new,要實作讀寫互斥關系,要誕生于一把讀寫鎖。

否則兩鎖之間的關系就不能建立了。

讀操作,就加讀鎖。寫操作就加寫鎖。

import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.ReadLock;
import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;

public class UseReentrantReadWriteLock {

  private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
  private ReadLock readLock = rwLock.readLock();
  private WriteLock writeLock = rwLock.writeLock();
  
  public void read(){
    try {
      readLock.lock();
      System.out.println("目前線程:" + Thread.currentThread().getName() + "進入...");
      Thread.sleep(3000);
      System.out.println("目前線程:" + Thread.currentThread().getName() + "退出...");
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      readLock.unlock();
    }
  }
  
  public void write(){
    try {
      writeLock.lock();
      System.out.println("目前線程:" + Thread.currentThread().getName() + "進入...");
      Thread.sleep(3000);
      System.out.println("目前線程:" + Thread.currentThread().getName() + "退出...");
    } catch (Exception e) {
      e.printStackTrace();
    } finally {
      writeLock.unlock();
    }
  }
  
  public static void main(String[] args) {
    
    final UseReentrantReadWriteLock urrw = new UseReentrantReadWriteLock();
    
    Thread t1 = new Thread(new Runnable() {
      @Override
      public void run() {
        urrw.read();
      }
    }, "t1");
    Thread t2 = new Thread(new Runnable() {
      @Override
      public void run() {
        urrw.read();
      }
    }, "t2");
    Thread t3 = new Thread(new Runnable() {
      @Override
      public void run() {
        urrw.write();
      }
    }, "t3");
    Thread t4 = new Thread(new Runnable() {
      @Override
      public void run() {
        urrw.write();
      }
    }, "t4");   
    
//    t1.start();
//    t2.start();
    
//    t1.start(); // R 
//    t3.start(); // W
    
    t3.start();
    t4.start();
    
    
  }
}      

鎖的總結

  • 避免死鎖
  • 減小鎖的持有時間
  • 減小鎖的粒度
  • 鎖的分離
  • 盡量使用無鎖的操作。原子操作,volatile等。