鎖如何使用?有什么注意事項?


Java 中常見的鎖有

  • synchronized
  • 可重入鎖 java.util.concurrent.lock.ReentrantLock
  • 可重復讀寫鎖 java.util.concurrent.lock.ReentrantReadWriteLock

 

synchronized 有 3種用法

  • 修飾普通方法,執行方法代碼,需要獲取對象本身 this 的鎖
package constxiong.concurrency.a18;

import java.util.ArrayList;
import java.util.List;

/**
 * 測試 synchronized 普通方法
 * @author ConstXiong
 * @date 2019-09-19 10:49:46
 */
public class TestSynchronizedNormalMethod {

    private int count = 0;
    
//    private void add1000() {
    private synchronized void add1000() { //使用 synchronized 修飾 add100 方法,即可獲得正確的值 30000
        for (int i = 0; i <1000; i++) {
            count++;
        }
    }
    
    //啟動 30 個線程,每個線程 對 TestSynchronized 對象的 count 屬性加 1000
    private void test() throws InterruptedException {
        List<Thread> threads = new ArrayList<Thread>(10);
        
        for (int i = 0; i <30; i++) {
            Thread t =  new Thread(() -> {
                add1000();
            });
            t.start();
            threads.add(t);
        }
        
        //等待所有線程執行完畢
        for (Thread t : threads) {
            t.join();
        }
        
        //打印 count 的值
        System.out.println(count);
    }
    
    public static void main(String[] args) throws InterruptedException {
        //創建 TestSynchronizedNormalMethod 對象,調用 test 方法
        new TestSynchronizedNormalMethod().test();
    }
}

 

 

  • 修飾靜態方法,執行方法代碼,需要獲取 class 對象的鎖
package constxiong.concurrency.a18;

import java.util.ArrayList;
import java.util.List;

/**
 * 測試 synchronized 靜態方法
 * @author ConstXiong
 * @date 2019-09-19 10:49:46
 */
public class TestSynchronizedStaticMethod {

    private static int count = 0;
    
    private static void add1000() {
//    private synchronized static void add1000() { //使用 synchronized 修飾 add100 方法,即可獲得正確的值 30000
        for (int i = 0; i <1000; i++) {
            count++;
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        
        //啟動 30 個線程,每個線程 對 TestSynchronized 對象的 count 屬性加 1000
        List<Thread> threads = new ArrayList<Thread>(10);
        
        for (int i = 0; i <30; i++) {
            Thread t =  new Thread(() -> {
                add1000();
            });
            t.start();
            threads.add(t);
        }
        
        //等待所有線程執行完畢
        for (Thread t : threads) {
            t.join();
        }
        
        //打印 count 的值
        System.out.println(count);
    }
}

 

 

  • 鎖定 Java 對象,修飾代碼塊,顯示指定需要獲取的 Java 對象鎖
package constxiong.concurrency.a18;

import java.util.ArrayList;
import java.util.List;

/**
 * 測試 synchronized 代碼塊
 * @author ConstXiong
 * @date 2019-09-19 10:49:46
 */
public class TestSynchronizedCodeBlock {

    private int count = 0;
    
    //鎖定的對象
    private final Object obj = new Object();
    
    private void add1000() {
        
        //執行下面的加 1000 的操作,都需要獲取 obj 這個對象的鎖
        synchronized (obj) {
            for (int i = 0; i <1000; i++) {
                count++;
            }
        }
    }
    
    //啟動 30 個線程,每個線程 對 TestSynchronized 對象的 count 屬性加 1000
    private void test() throws InterruptedException {
        List<Thread> threads = new ArrayList<Thread>(10);
        
        for (int i = 0; i <30; i++) {
            Thread t =  new Thread(() -> {
                add1000();
            });
            t.start();
            threads.add(t);
        }
        
        //等待所有線程執行完畢
        for (Thread t : threads) {
            t.join();
        }
        
        //打印 count 的值
        System.out.println(count);
    }
    
    public static void main(String[] args) throws InterruptedException {
        //創建 TestSynchronizedNormalMethod 對象,調用 test 方法
        new TestSynchronizedCodeBlock().test();
    }
}

 

 

 

可重入鎖 java.util.concurrent.lock.ReentrantLock 的使用示例

package constxiong.concurrency.a18;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 測試 ReentrantLock 
 * @author ConstXiong
 * @date 2019-09-19 11:26:50
 */
public class TestReentrantLock {

    private int count = 0;
    
    private final Lock lock = new ReentrantLock();
    
    private void add1000() {
        lock.lock();
        try {
            for (int i = 0; i <1000; i++) {
                count++;
            }
        } finally {
            lock.unlock();
        }
    }
    
    //啟動 30 個線程,每個線程 對 TestSynchronized 對象的 count 屬性加 1000
    private void test() throws InterruptedException {
        List<Thread> threads = new ArrayList<Thread>(10);
        
        for (int i = 0; i <30; i++) {
            Thread t =  new Thread(() -> {
                add1000();
            });
            t.start();
            threads.add(t);
        }
        
        //等待所有線程執行完畢
        for (Thread t : threads) {
            t.join();
        }
        
        //打印 count 的值
        System.out.println(count);
    }
    
    public static void main(String[] args) throws InterruptedException {
        //創建 TestReentrantLock 對象,調用 test 方法
        new TestReentrantLock().test();
    }
    
}

 

 

 

可重復讀寫鎖 java.util.concurrent.lock.ReentrantReadWriteLock 的使用示例

package constxiong.concurrency.a18;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
 
/**
 * 測試可重入讀寫鎖 ReentrantReadWriteLock
 * @author ConstXiong
 * @date 2019-09-19 11:36:19
 */
public class TestReentrantReadWriteLock {
    
    //存儲 key value 的 map
    private Map<String, Object> map = new HashMap<String, Object>();
    
    //讀寫鎖
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    
    /**
     * 根據 key 獲取 value
     * @param key
     */
    public Object get(String key) {
        Object value = null;
        lock.readLock().lock();
        try {
            Thread.sleep(50L);
            value = map.get(key);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
        return value; 
    }
    
    /**
     * 設置key-value
     * @param key
     */
    public void set(String key, Object value) {
        lock.writeLock().lock();
        try {
            Thread.sleep(50L);
            map.put(key, value);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }
 
    //測試5個線程讀數據,5個線程寫數據
    public static void main(String[] args) {
        //創建測試可重入讀寫鎖 TestReentrantReadWriteLock 對象
        TestReentrantReadWriteLock test = new TestReentrantReadWriteLock();
        
        String key = "lock";//存入 map 中的 key
        Random r = new Random();//生成隨機數作為 value
        
        for (int i = 0; i <5; i++) {
            //5 個線程讀 map 中 key 的 value
            new Thread(() -> {
                for (int j = 0; j <10; j++) {
                    System.out.println(Thread.currentThread().getName() + " read value=" + test.get(key));
                }
            }).start();
            
            //5 個線程寫 map 中 key 的 value
            new Thread(() -> {
                for (int j = 0; j <10; j++) {
                    int value = r.nextInt(1000);
                    test.set(key, value);
                    System.out.println(Thread.currentThread().getName() + " write value=" + value);
                }
            }).start();
        }
    }
    
}

 

 

鎖的使用注意事項

  • synchronized 修飾代碼塊時,最好不要鎖定基本類型的包裝類,如 jvm 會緩存 -128 ~ 127 Integer 對象,每次向如下方式定義 Integer 對象,會獲得同一個 Integer,如果不同地方鎖定,可能會導致詭異的性能問題或者死鎖
Integer i = 100; 
  • synchronized 修飾代碼塊時,要線程互斥地執行代碼塊,需要確保鎖定的是同一個對象,這點往往在實際編程中會被忽視
  • synchronized  不支持嘗試獲取鎖、鎖超時和公平鎖
  • ReentrantLock 一定要記得在 finally{} 語句塊中調用 unlock() 方法釋放鎖,不然可能導致死鎖
  • ReentrantLock 在並發量很高的情況,由於自旋很消耗 CPU 資源
  • ReentrantReadWriteLock 適合對共享資源寫操作很少,讀操作頻繁的場景;可以從寫鎖降級到讀鎖,無法從讀鎖升級到寫鎖


 


 

所有資源資源匯總於公眾號


 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM