Lock與Synchronized小結

【區別】synchronized:對象鎖,調用synchronized方法的線程取得該對象鎖後,其他線程需要等待該線程釋放該對象鎖後才能進入該方法。若是持有不同的對象的線程,則可以同時進入該方法。注意:類鎖(全局鎖)synchronized(A.getClass)與對象鎖synchronized(this)的區別【特點】隱式鎖,鎖的範圍是整個方法或代碼塊中,離開該方法/塊後自動釋放鎖

2.重入鎖:ReentrantLock:更靈活的鎖機制,提供可輪詢和可中斷、可定時的鎖獲取機制。持有該鎖的線程可以重複進入該方法(count加1,但只執行一次Method),可以實現更細微的鎖顆粒。【特點】需要顯式的獲取鎖和釋放鎖

使用事例:

class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds  <span style="font-family: Arial, Helvetica, sans-serif;">一次只能有一個線程進入,鎖的顆粒較大</span>
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }
鎖的顆粒對併發的控制作用是明顯的,例如兩個線程對相同的一個屬性進行操作,A轉賬B的兩個線程中,一個線程需要等待另一個線程釋放該鎖才能進入方法,這樣是沒有問題的。但是兩個線程一個是A轉賬B,一個是C轉賬D,上面的鎖的方法也會等待一個線程執行完畢釋放鎖後,另一個線程才能獲取鎖然後執行,這樣就比較慢了,線程只能排隊執行,不能併發執行。因此需要改進鎖實現方法,實現線程合理的併發控制。實現方法(屬性上加鎖操作,減少鎖的顆粒):
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantLock;
/**
 * @Author : hd
 * @Date : 2016/8/18 16:27
 * @Version : 1.0.0
 */
/**
 * 分段鎖,系統提供一定數量的原始鎖,根據傳入對象的哈希值獲取對應的鎖並加鎖
 * 注意:要鎖的對象的哈希值如果發生改變,有可能導致鎖無法成功釋放!!!
 */
public class SegmentLock<T> {

    private Integer segments = 16;//默認分段數量
    private final Map<Integer, ReentrantLock> lockMap = new HashMap<>();

    public SegmentLock() {
        init(null, false);
    }

    public SegmentLock(Integer counts, boolean fair) {
        init(counts, fair);
    }

    private void init(Integer counts, boolean fair) {
        if (counts != null) {
            segments = counts;
        }
        for (int i = 0; i < segments; i++) {
            lockMap.put(i, new ReentrantLock(fair));
        }
    }

    public void lock(T key) {
        ReentrantLock lock = lockMap.get(key.hashCode() % segments);
        lock.lock();
    }

    public void unlock(T key) {
        ReentrantLock lock = lockMap.get(key.hashCode() % segments);
        lock.unlock();
    }
}
另一個事例:三個線程同時進入該鎖的情況
public class Test {
    //public static volatile int i=10;
    static int i=10;
    @org.junit.Test
    public void testReenTrant(){
        final  SegmentLock<String> segmentLock=new SegmentLock<String>();
        final String key="2016";
        for(int j=0;j<3;j++){
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try{
                        segmentLock.lock(key);
                        i=i-1;
                        System.out.println("lock i:"+i);
                    }finally {
                        segmentLock.unlock(key);
                    }
                    System.out.println("unlock i:"+i);
                }
            }).start();
        }
    }
}
/**
 結果:lock i:9
 unlock i:9
 lock i:8
 unlock i:8
根據結果可知:一個線程獲取鎖進入方法執行後,釋放了該鎖,另外兩個線程重複獲取到了鎖

synchronized例子:

class Sync {  
  
    public synchronized void test() {  
        System.out.println("test開始..");  
        try {  
            Thread.sleep(1000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
        System.out.println("test結束..");  
    }  
}  
  
class MyThread extends Thread {  
  
    public void run() {  
        Sync sync = new Sync();  
        sync.test();  
    }  
}  
  
public class Main {  
  
    public static void main(String[] args) {  
        for (int i = 0; i < 3; i++) {  
            Thread thread = new MyThread();  
            thread.start();  
        }  
    }  
}  
運行結果://持有不同對象鎖 的線程同時進入
test開始..
test開始..
test開始..
test結束..
test結束..
test結束..

class MyThread extends Thread {  
  
    private Sync sync;  
  
    public MyThread(Sync sync) {  
        this.sync = sync;  
    }  
  
    public void run() {  
        sync.test();  
    }  
}  
  
public class Main {  
  
    public static void main(String[] args) {  
        Sync sync = new Sync();  
        for (int i = 0; i < 3; i++) {  
            Thread thread = new MyThread(sync);  
            thread.start();  
        }  
    }  
}  
運行結果://持有相同對象鎖 的線程要等另一個線程的對象鎖釋放才能進入
test開始..
test結束..
test開始..
test結束..
test開始..
test結束..
class Sync {  
  
    public void test() {  
        synchronized (Sync.class) {  
            System.out.println("test開始..");  
            try {  
                Thread.sleep(1000);  
            } catch (InterruptedException e) {  
                e.printStackTrace();  
            }  
            System.out.println("test結束..");  
        }  
    }  
}  
  
class MyThread extends Thread {  
  
    public void run() {  
        Sync sync = new Sync();  
        sync.test();  
    }  
}  
  
public class Main {  
  
    public static void main(String[] args) {  
        for (int i = 0; i < 3; i++) {  
            Thread thread = new MyThread();  
            thread.start();  
        }  
    }  
}  
運行結果://對象類鎖,無論創建了多少對象都是屬於這個類,需要等待這個線程釋放鎖,其他線程才能進入,全局鎖
test開始..
test結束..
test開始..
test結束..
test開始..
test結束..

【總結】開發中,常常需要對鎖的粒度對象細化來實現符合邏輯併發控制,ReentrantLock實現了區別於Synchronized的另一種鎖的機制,正如api解釋的一樣

A reentrant mutual exclusion Lock with the same basic behavior and semantics as the implicit monitor lock accessed using synchronized <span style="font-family: Arial, Helvetica, sans-serif;">methods and statements, but with extended capabilities.</span>

和使用synchronized(隱式監視器鎖)和語句訪問具有相同的基本行爲和語義的重入的互斥鎖,但功能更強大。

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