- 同步控制是並發程序必不可少的重要手段,synchronized關鍵字就是一種簡單的控制方式,除此之外,JDK內部並發包中也也提供了Lock接口,該接口中提供了lock()方法和unLock()方法對顯式加鎖和顯式釋放鎖操作進行支持。
ReentrantLock(重入鎖)
重入鎖可以完全替代synchronized關鍵字,在jdk5早期版本中重入鎖的性能遠遠好於synchronized,但從JDK6開始JDK在synchronized中做了大量的優化,是的兩者的性能差距不大,
public class Test { public static ReentrantLock lock = new ReentrantLock(); public static int i = 0; public static void increase() { try { lock.lock(); i++; }finally { lock.unlock(); } } public static void main(String[] args) throws Exception { Thread t1 = new TestThread(); Thread t2 = new TestThread(); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(i); } } class TestThread extends Thread { @Override public void run() { for (int j = 0; j < 1000; j++) { Test.increase(); } } }
從這段代碼可以看到與synchronized相比,重入鎖有着顯示的操作過程,我們需要手動定義核實加鎖,核實釋放鎖,但也就是因為這樣,重入鎖對邏輯的控制靈活性要好於synchronized。
公平鎖
大多數情況下鎖的申請都是非公平的。如一個線程1先請求了鎖A,然后線程2頁也請求了鎖A,那么當鎖A可用時,是線程1可以獲得鎖還是線程2是不一定的,系統只是會從這個鎖的等待隊列中隨機挑選一個。
重入鎖允許我們對其公平性進行設置。公平鎖的一大特點是:它不會產生飢餓現象。只要排隊,最終你就可以獲得資源。可以使用如下構造函數創建公平鎖:
public ReentrantLock(boolean fair)
當參數fair為true,表示鎖的公平的,當然由於公平所需要維護有序隊列,因此公平鎖的實現成本比較高,性能相對也底下,所以默認都是非公平鎖
public class Test { public static ReentrantLock lock = new ReentrantLock(true); public static void main(String[] args) throws Exception { Thread t1 = new TestThread(); Thread t2 = new TestThread(); t1.start(); t2.start(); } } class TestThread extends Thread { @Override public void run() { while(true) try { Test.lock.lock(); System.out.println(Thread.currentThread().getName()+"獲得鎖"); } finally{ Test.lock.unlock(); } } }
可以看到如上代碼制定公平鎖之后,兩個線程交替獲得鎖
Thread-1獲得鎖 Thread-0獲得鎖 Thread-1獲得鎖 Thread-0獲得鎖 Thread-1獲得鎖 Thread-0獲得鎖 Thread-1獲得鎖 Thread-0獲得鎖 Thread-1獲得鎖 Thread-0獲得鎖 Thread-1獲得鎖 Thread-0獲得鎖 Thread-1獲得鎖 ...
ReentrantLock的一些其他方法:
public boolean tryLock(); //使用此方法,當前線程會嘗試獲得鎖,如果鎖未被其他線程占用,則申請鎖成功,返回true,否則會立即返回false.這種模式不會引起線程等待,因此也不會產生死鎖。
public boolean tryLock(long timeout, TimeUnit unit) //在這里tryLock接收兩個參數,一個表示等待時長,一個表示計時單位,表示在一段時間范圍內如果得到鎖就返回true,否則直接返回false,不在繼續等待鎖。
public class Test implements Runnable { public static ReentrantLock lock = new ReentrantLock(); @Override public void run() { try { if (lock.tryLock(5, TimeUnit.SECONDS)) { System.out.println(Thread.currentThread().getName()); System.out.println("get lock success"); Thread.sleep(6000); } else { System.out.println(Thread.currentThread().getName()); System.out.println("get lock failed"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { if (lock.isHeldByCurrentThread()) { lock.unlock(); } } } public static void main(String args[]) { Test timeLock = new Test(); Thread thread1 = new Thread(timeLock); Thread thread2 = new Thread(timeLock); thread1.start(); thread2.start(); } }
輸出結果:
Thread-0 get lock success Thread-1 get lock failed
其他:
關於重入鎖的具體原理及這部分源碼的分析可以看下這篇文章http://www.cnblogs.com/xrq730/p/4979021.html
ReentrantReadWriteLock(讀寫鎖)
ReadWriteLock是JDK5開始提供的讀寫分離鎖。讀寫分離開有效的幫助減少鎖的競爭,以提升系統性能。用鎖分離的機制避免多個讀操作線程之間的等待。
讀寫鎖的訪問約束:
- 讀-讀不互斥:讀讀之間不阻塞
- 讀-寫互斥:讀堵塞寫,寫也阻塞讀
- 寫-寫互斥:寫寫阻塞
如果在一個系統中讀的操作次數遠遠大於寫操作,那么讀寫鎖就可以發揮明顯的作用,提升系統性能
public class Test { private static Lock lock = new ReentrantLock(); private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(); private static Lock readLock = reentrantReadWriteLock.readLock(); private static Lock writeLock = reentrantReadWriteLock.writeLock(); private static int value; public static Object handleRead(Lock lock) throws InterruptedException { try { lock.lock(); Thread.sleep(1000);// 模擬讀操作 System.out.println("讀操作:" + value); return value; } finally { lock.unlock(); } } public static void handleWrite(Lock lock, int index) throws InterruptedException { try { lock.lock(); Thread.sleep(1000);// 模擬寫操作 System.out.println("寫操作:" + value); value = index; } finally { lock.unlock(); } } public static void main(String[] args) throws Exception { TestReadThread testReadThread = new TestReadThread(); TestWriteThread testWriteThread = new TestWriteThread(); for (int i = 0; i < 18; i++) { new Thread(testReadThread).start(); } for (int i = 18; i < 20; i++) { new Thread(testWriteThread).start(); } } private static class TestReadThread extends Thread { @Override public void run() { try { //Test.handleRead(lock); Test.handleRead(readLock); } catch (InterruptedException e) { e.printStackTrace(); } } } private static class TestWriteThread extends Thread { @Override public void run() { try { //Test.handleWrite(lock,new Random().nextInt(100)); Test.handleWrite(writeLock,new Random().nextInt(100)); } catch (InterruptedException e) { e.printStackTrace(); } } } }
這一段代碼可以清楚的表達讀寫鎖的作用,如果不使用讀寫鎖,那么整個程序執行時間大概是20s。換用讀寫鎖后只需要2s多,這里讀線程完全並行,節省了大部分時間。