synchronized 和 java.util.concurrent.lock.Lock 之間的區別
- 實現層面不一樣。synchronized 是 Java 關鍵字,JVM層面 實現加鎖和釋放鎖;Lock 是一個接口,在代碼層面實現加鎖和釋放鎖
- 是否自動釋放鎖。synchronized 在線程代碼執行完或出現異常時自動釋放鎖;Lock 不會自動釋放鎖,需要在 finally {} 代碼塊顯式地中釋放鎖
- 是否一直等待。synchronized 會導致線程拿不到鎖一直等待;Lock 可以設置嘗試獲取鎖或者獲取鎖失敗一定時間超時
- 獲取鎖成功是否可知。synchronized 無法得知是否獲取鎖成功;Lock 可以通過 tryLock 獲得加鎖是否成功
- 功能復雜性。synchronized 加鎖可重入、不可中斷、非公平;Lock 可重入、可判斷、可公平和不公平、細分讀寫鎖提高效率
java.util.concurrent.lock.Lock 與 java.util.concurrent.lock.ReadWriteLock 之間的區別
- ReadWriteLock 定義了獲取讀鎖和寫鎖的接口,讀鎖之間不互斥,非常適合讀多、寫少的場景
適用場景
- JDK 1.6 開始,對 synchronized 方式枷鎖進行了優化,加入了偏向鎖、輕量級鎖和鎖升級機制,性能得到了很大的提升。性能與 ReentrantLock 差不多
- 讀多寫少的情況下,考慮使用 ReadWriteLock
synchronized、ReentrantLock、ReentrantReadWriteLock 啟動 990 個線程讀共享變量,10 個線程寫共享變量
package constxiong.concurrency.a020; import java.util.ArrayList; import java.util.List; import java.util.UUID; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * JDK 1.8 中鎖性能的測試 * @author ConstXiong */ public class TestLockPerformance { public static Object obj = new Object();//用於 synchronized 獲取鎖 public static Lock lock = new ReentrantLock();//可重入鎖 public static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();//讀寫鎖 public static final int READ = 0; public static final int WRITE = 1; // uuid,一個隨機字符串 public static String uuid = UUID.randomUUID().toString(); public static void main(String[] args) throws InterruptedException { // testSynchronized(1000); testReentrantLock(1000); // testReadWriteLock(1000); } public static void testSynchronized(int threadNum) throws InterruptedException { long t1 = System.currentTimeMillis(); List<Thread> tList = new ArrayList<Thread>(); //啟動 threadNum - 向上取整 (0.01 * threadNum) 個線程讀 uuid, 向上取整 (0.01 * threadNum) 個線程寫 uuid for (int i = 0; i < threadNum; i++) { Thread t; if (i % 100 == 0) { t = new Thread(new WorkerSynchronized(WRITE)); } else { t = new Thread(new WorkerSynchronized(READ)); } t.start();//啟動線程 tList.add(t); } for (Thread t : tList) { t.join(); } long t2 = System.currentTimeMillis(); System.out.println("testSynchronized 耗時:" + (t2 - t1)); } public static void testReentrantLock(int threadNum) throws InterruptedException { long t1 = System.currentTimeMillis(); List<Thread> tList = new ArrayList<Thread>(); //啟動 threadNum - 向上取整 (0.01 * threadNum) 個線程讀 uuid, 向上取整 (0.01 * threadNum) 個線程寫 uuid for (int i = 0; i < threadNum; i++) { Thread t; if (i % 100 == 0) { t = new Thread(new WorkerReentrantLock(WRITE)); } else { t = new Thread(new WorkerReentrantLock(READ)); } t.start();//啟動線程 tList.add(t); } for (Thread t : tList) { t.join(); } long t2 = System.currentTimeMillis(); System.out.println("testReentrantLock 耗時:" + (t2 - t1)); } public static void testReadWriteLock(int threadNUm) throws InterruptedException { long t1 = System.currentTimeMillis(); List<Thread> tList = new ArrayList<Thread>(); //啟動 threadNum - 向上取整 (0.01 * threadNum) 個線程讀 uuid, 向上取整 (0.01 * threadNum) 個線程寫 uuid for (int i = 0; i < threadNUm; i++) { Thread t; if (i % 100 == 0) { t = new Thread(new WorkerReadWriteLock(WRITE)); } else { t = new Thread(new WorkerReadWriteLock(READ)); } t.start();//啟動線程 tList.add(t); } for (Thread t : tList) { t.join(); } long t2 = System.currentTimeMillis(); System.out.println("testReadWriteLock 耗時:" + (t2 - t1)); } } //工作線程,使用 synchronized 關鍵字加鎖 class WorkerSynchronized implements Runnable { //0-read;1-write private int type; WorkerSynchronized(int type) { this.type = type; } //加鎖讀 TestLockPerformance.uuid 變量,並打印 private void read() { synchronized (TestLockPerformance.obj) { //休眠 20 毫秒,模擬任務執行耗時 try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " read uuid = " + TestLockPerformance.uuid); } } //加鎖寫 TestLockPerformance.uuid 變量,並打印 private void write() { synchronized (TestLockPerformance.obj) { //休眠 20 毫秒,模擬任務執行耗時 try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } TestLockPerformance.uuid = UUID.randomUUID().toString(); System.out.println(Thread.currentThread().getName() + " write uuid = " + TestLockPerformance.uuid); } } @Override public void run() { //type = 0,線程讀 TestLockPerformance.uuid 變量 if (type == 0) { read(); //type = 1,線程生成 uuid,寫入 TestLockPerformance.uuid 變量 } else { write(); } } } //工作線程,使用 ReentrantLock 加鎖 class WorkerReentrantLock implements Runnable { //0-read;1-write private int type; WorkerReentrantLock(int type) { this.type = type; } //加鎖讀 TestLockPerformance.uuid 變量,並打印 private void read() { TestLockPerformance.lock.lock(); try { //休眠 20 毫秒,模擬任務執行耗時 try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " read uuid = " + TestLockPerformance.uuid); } finally { TestLockPerformance.lock.unlock(); } } //加鎖寫 TestLockPerformance.uuid 變量,並打印 private void write() { TestLockPerformance.lock.lock(); try { //休眠 20 毫秒,模擬任務執行耗時 try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } TestLockPerformance.uuid = UUID.randomUUID().toString(); System.out.println(Thread.currentThread().getName() + " write uuid = " + TestLockPerformance.uuid); } finally { TestLockPerformance.lock.unlock(); } } @Override public void run() { //type = 0,線程讀 TestLockPerformance.uuid 變量 if (type == 0) { read(); //type = 1,線程生成 uuid,寫入 TestLockPerformance.uuid 變量 } else { write(); } } } //工作線程,使用 ReentrantReadWriteLock 關鍵字加鎖 class WorkerReadWriteLock implements Runnable { //0-read;1-write private int type; WorkerReadWriteLock(int type) { this.type = type; } //加鎖讀 TestLockPerformance.uuid 變量,並打印 private void read() { TestLockPerformance.readWriteLock.readLock().lock(); try { //休眠 20 毫秒,模擬任務執行耗時 try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " read uuid = " + TestLockPerformance.uuid); } finally { TestLockPerformance.readWriteLock.readLock().unlock(); } } //加鎖寫 TestLockPerformance.uuid 變量,並打印 private void write() { TestLockPerformance.readWriteLock.writeLock().lock(); try { //休眠 20 毫秒,模擬任務執行耗時 try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } TestLockPerformance.uuid = UUID.randomUUID().toString(); System.out.println(Thread.currentThread().getName() + " write uuid = " + TestLockPerformance.uuid); } finally { TestLockPerformance.readWriteLock.writeLock().unlock(); } } @Override public void run() { //type = 0,線程讀 TestLockPerformance.uuid 變量 if (type == 0) { read(); //type = 1,線程生成 uuid,寫入 TestLockPerformance.uuid 變量 } else { write(); } } }
調用測試方法
testSynchronized(1000);
耗時
Thread-0 write uuid = b7fb63d7-79cc-4cc0-84ed-5a9cd4de6824 Thread-252 read uuid = b7fb63d7-79cc-4cc0-84ed-5a9cd4de6824 Thread-251 read uuid = b7fb63d7-79cc-4cc0-84ed-5a9cd4de6824 . . . Thread-255 read uuid = d666bfe6-dc71-4df2-882a-d530a59d7e92 Thread-254 read uuid = d666bfe6-dc71-4df2-882a-d530a59d7e92 Thread-253 read uuid = d666bfe6-dc71-4df2-882a-d530a59d7e92 testSynchronized 耗時:22991
調用測試方法
testReentrantLock(1000);
耗時
Thread-0 write uuid = 4352eb13-d284-47ec-8caa-fc81d91d08e1 Thread-1 read uuid = 4352eb13-d284-47ec-8caa-fc81d91d08e1 Thread-485 read uuid = 4352eb13-d284-47ec-8caa-fc81d91d08e1 . . . Thread-997 read uuid = 9d7f0a78-5eb7-4506-9e98-e8e9a7a717a5 Thread-998 read uuid = 9d7f0a78-5eb7-4506-9e98-e8e9a7a717a5 Thread-999 read uuid = 9d7f0a78-5eb7-4506-9e98-e8e9a7a717a5 testReentrantLock 耗時:22935
調用測試方法
testReadWriteLock(1000);
耗時
Thread-0 write uuid = 81c13f80-fb19-4b27-9d21-2e99f8c8acbd Thread-277 read uuid = 81c13f80-fb19-4b27-9d21-2e99f8c8acbd Thread-278 read uuid = 81c13f80-fb19-4b27-9d21-2e99f8c8acbd . . . Thread-975 read uuid = 35be0359-1973-4a4f-85b7-918053d841f7 Thread-971 read uuid = 35be0359-1973-4a4f-85b7-918053d841f7 Thread-964 read uuid = 35be0359-1973-4a4f-85b7-918053d841f7 testReadWriteLock 耗時:543
通過耗時測試可以看出,使用 synchronized 和 ReentrantLock 耗時相近;但是由於 990 個線程讀,10 個線程寫,使用 ReentrantReadWriteLock 耗時 543 毫秒。
PS:JDK 並發包里的工具類,還有很多適合特定場景的工具,后面我們繼續探索。