java鎖:synchronized、ReadWriteLock、ReentrantReadWriteLock*

1、先讀這篇文章,瞭解synchronized:Java線程同步:synchronized鎖住的是代碼仍是對象java

  1. synchronizied,默認是synchronized(this),括號裏的內容能夠當作是鎖。
  2. 把鎖當成對象看待,在類C中加鎖的代碼塊A,能鎖住代碼塊A的鎖有不少,new出C類的對象c一、c2,默認就是synchronized(c1){A}、synchronized(c2){A},在對象c1中,得到了c1的鎖才能執行代碼。在對象c2中,得到了c2的鎖才能執行代碼。相同的代碼塊A,在不一樣的對象c一、c2中,是能夠同時執行的,可是在對象c1中,A是不能執行的(c2也同理)。
  3. 在類C中加鎖的代碼塊A,synchronized(C.class){A},這時鎖是C的class對象,至關於在一個jvm中,只有一個C的class對象,因此這時候就是全局鎖了,在整個jvm中,代碼A只能有一個併發。
  4. 加鎖的代碼是會影響併發的,因此加鎖的內容能縮小就儘可能縮小。
  5. 若是在一個對象中,有多個synchronized代碼塊A,B,C,則A如今正被鎖着,B和C也會被同時鎖住。

2、synchronized加鎖形成的問題
對象的方法中一旦加入synchronized修飾,則這個對象任什麼時候刻只能有一個線程訪問synchronized修飾的方法。假設有個數據對象擁有寫方法與讀方法,多線程環境中要想保證數據的安全,需對該對象的讀寫方法都要加入 synchronized同步塊。這樣任何線程在寫入時,其它線程沒法讀取與改變數據;若是有線程在讀取時,其餘線程也沒法讀取或寫入。這種方式在寫入操做遠大於讀操做時,問題不大,而當讀取遠遠大於寫入時,會形成性能瓶頸,由於此種狀況下讀取操做是能夠同時進行的,而加鎖操做限制了數據的併發讀取。這就引出了ReadWriteLock,讀寫鎖:分爲讀鎖和寫鎖,多個讀鎖不互斥,讀鎖與寫鎖互斥,這是由jvm本身控制的,你只要上好相應的鎖便可。若是你的代碼只讀數據,能夠不少人同時讀,但不能同時寫,那就上讀鎖;若是你的代碼修改數據,只能有一我的在寫,且不能同時讀取,那就上寫鎖。總之,讀的時候上讀鎖,寫的時候上寫鎖!
可是ReadWriteLock是個接口,ReentrantReadWriteLock是他的實現類,ReentrantReadWriteLock特性以下:安全

  • ReentrantReadWriteLock和synchronized同樣,不一樣的對象是不一樣的鎖,一個對象產生的鎖,只能自己的對象。
  • 重入方面其內部的WriteLock能夠獲取ReadLock,可是反過來ReadLock想要得到WriteLock則永遠都不要想。
  • WriteLock能夠降級爲ReadLock,順序是:先得到WriteLock再得到ReadLock,而後釋放WriteLock,這時候線程將保持Readlock的持有。反過來ReadLock想要升級爲WriteLock則不可能。
  • ReadLock能夠被多個線程持有而且在做用時排斥任何的WriteLock,而WriteLock則是徹底的互斥。這一特性最爲重要,由於對於高讀取頻率而相對較低寫入的數據結構,使用此類鎖同步機制則能夠提升併發量。

參考:ReentrantReadWriteLock讀寫鎖的使用
3、ReentrantReadWriteLock實例
ReentrantReadWriteLock實現了ReadWriteLock,使用ReentrantReadWriteLock,只須要在對象O(或者某個數據結構)中定義ReentrantReadWriteLock對象便可,而後調用lock或者unlock,便可對對象O實現加鎖操做。數據結構

package com.sf.log_gen;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class LockTest {

  public static void main(String[] args) {
    Count count = new Count();
    new Thread(new Write(count)).start();
    // 由於是不一樣的對象,因此這裏立馬就能得到ReadLock
    System.out.println(new Count().get());
    // 針對同一對象to,由於子線程先得到了對象to的WriteLock(也有可能主線程先得到,主線程最好sleep(100L)),
    // 因此這裏ReadLock就須要等WriteLock釋放才能得到
    System.out.println(count.get());
  }
}


class Write implements Runnable {
  Count count = null;

  public Write(Count count) {
    this.count = count;
  }

  public void run() {// 在這個線程中先得到了writeLock
    this.count.lock();
    this.count.add();
    this.count.unlock();
  }
}


class Count {
  private ReadWriteLock rwl = new ReentrantReadWriteLock();
  int num = 0;

  public void add() {
    try {
      Thread.sleep(4000L);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    num++;
  }

  public int get() {// get方法若是不加鎖,則全部線程能夠直接讀取
    this.rwl.readLock().lock();
    int ret = num;
    this.rwl.readLock().unlock();
    return ret;
  }

  public void lock() {
    this.rwl.writeLock().lock();
  }

  public void unlock() {
    this.rwl.writeLock().unlock();
  }
}

4、悲觀鎖樂觀鎖 上面的鎖都是悲觀鎖
樂觀鎖即我先對num操做,操做完了以後,在判斷num是否有變化,沒變化,則說明沒有別的線程在對num操做,那我就能夠把我操做後的值賦值給num了。多線程

相關文章
相關標籤/搜索