1:獲取Lock鎖的幾種方式
前面說了synchronized有鎖對象和鎖類對象,當某個線程獲取鎖其他線程必須等待執(zhí)行完畢才可繼續(xù)進行,比如線程A先獲取鎖,但是出現(xiàn)異常導致的后果就是線程B無法獲取鎖,會出現(xiàn)死鎖的情況(http://www.cnblogs.com/LipeiNet/p/6475851.html),那么我們一起看看Lock是如何解決的。lock有4種方式來獲取鎖
1:lock.lock() 如果獲取了鎖立即返回,如果別的線程持有鎖,當前線程則一直處于休眠狀態(tài),直到獲取鎖。此種模式和synchronized一樣但是不會出現(xiàn)死鎖
public class Lock1 { static int value = 0; static Lock lock = new ReentrantLock(); static class Task1 implements Runnable { public void run() { System.out.println("線程" + Thread.currentThread().getName() + "開始執(zhí)行"); lock.lock(); try { for (int i = 0; i < 1000000; i++) { value++; } System.out.println(value); } finally { lock.unlock(); } } } static class Task2 implements Runnable { public void run() { System.out.println("線程" + Thread.currentThread().getName() + "開始執(zhí)行"); lock.lock(); try { for (int i = 0; i < 1000000; i++) { value++; } System.out.println(value); } finally { lock.unlock(); } } } public static void main(String[] args) { ExecutorService service= Executors.newCachedThreadPool(); service.execute(new Task1()); service.execute(new Task2()); service.shutdown(); } }
輸出結(jié)果很明顯其中一個value是1000000,一個是2000000,效果和synchronized是一樣的。但是如果我們?nèi)サ鬺ock以后的結(jié)果呢,很明顯會錯,如下圖這樣
為啥會出現(xiàn)這樣情況呢,是由于cpu速度極快,每次處理完畢之后并沒有立即把數(shù)值放入Java內(nèi)存中,而是放在寫緩存區(qū),然后由寫緩存區(qū)同步到Java內(nèi)存中,這樣一樣,如果線程1計算結(jié)果是2,但是還是到內(nèi)存中,導致線程2以為value值還是1所以會重復計算,還有從結(jié)果我們也可以看出value值并不是100000說明2個線程是同步執(zhí)行的。
2:lock.tryLock();
這個方法和synchronized有所不同,synchronized和lock都會等待直到獲取鎖。如果獲取了鎖立即返回true,如果別的線程正持有鎖,立即返回false;當然我們可以利用while循環(huán)一直等待,直到獲取鎖然后進行。代碼如下
public class Lock2 { public static void main(String[] args) { final Lock lock = new ReentrantLock(); Thread t1 = new Thread(new Runnable() { public void run() { String tName = Thread.currentThread().getName(); while (!lock.tryLock()) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("等待獲取鎖"); } try { for (int i = 0; i < 5; i++) { System.out.println(tName + ":" + i); } Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }); Thread t2 = new Thread(new Runnable() { public void run() { String tName = Thread.currentThread().getName(); while (!lock.tryLock()) { try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("等待獲取鎖"); } try { for (int i = 0; i < 5; i++) { System.out.println(tName + ":" + i); } } catch (Exception e) { System.out.println(tName + "出錯了?。?!"); } finally { System.out.println(tName + "釋放鎖?。?quot;); lock.unlock(); } } }); t1.start(); t2.start(); } }
3:lock.trylock(long time, TimeUnit unit)如果獲取了鎖定立即返回true,如果別的線程正持有鎖,會等待參數(shù)給定的時間,在等待的過程中,如果獲取了鎖定,就返回true,如果等待超時,返回false;unit是time的時間單位比如TimeUnit.SECONDS就是表示秒
4:lock.lockInterruptibly()如果獲取了鎖定立即返回,如果沒有獲取鎖定,當前線程處于休眠狀態(tài),直到或者鎖定,或者當前線程被別的線程中斷。也就是說如何線程沒有被中斷和lock.lock()的作用一樣。但是如何線程被中斷了,那么此時這個線程不會有任何的響應,想象這么一個場景,線程A和線程B同時執(zhí)行任務,但是必須等待線程B先執(zhí)行,但是執(zhí)行過程中突然線程A突然被中斷,那么這個時候就可能出現(xiàn)死鎖,哪怕是在finally中加入unlock,這個時候我們就要采用lockInterruptibly()了。代碼如下
public class Lock3 { public static void main(String[] args) { final Lock lock = new ReentrantLock(); final Thread thread1 = new Thread(new Runnable() { public void run() { try { TimeUnit.SECONDS.sleep(2); System.out.println("等待被中斷"); lock.lockInterruptibly(); } catch (InterruptedException e) { System.out.println("我被中斷了"); } finally { lock.unlock(); } } }); Thread thread2 = new Thread(new Runnable() { public void run() { lock.lock(); try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { } thread1.interrupt(); System.out.println("線程1已經(jīng)被中斷"); } }); thread1.start(); thread2.start(); } }
2:讀鎖和寫鎖
在開發(fā)中我們最好的愿望就是寫的時候加鎖,但是讀的時候不加鎖這樣會大大的提升效率,但是采用synchronized卻無法滿足我們的要求,如果在讀的方法面前加鎖那么所有的讀都需要等待,如果不加鎖的話那么如果現(xiàn)在A,B2個線程讀取,C線程寫入可能導致的后果就是A,B2個線程取得數(shù)據(jù)不一致,明明同一種業(yè)務場景但是獲取值卻不同。好了lock的讀鎖和寫鎖幫助我們實現(xiàn)這種功能。
public class ReadWriteLockTest { private int value = 0; ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); public void add(int value) { Lock writeLock = readWriteLock.writeLock(); writeLock.lock(); try { TimeUnit.SECONDS.sleep(3); System.out.println("添加開始時間:" + new Date()); this.value += value; } catch (InterruptedException e) { e.printStackTrace(); } finally { writeLock.unlock(); } } public void getValue() { Lock readLock = readWriteLock.readLock(); readLock.lock(); try { TimeUnit.SECONDS.sleep(1); System.out.println("獲取開始時間:" + new Date()); System.out.println(value); } catch (InterruptedException e) { } finally { readLock.unlock(); } } public static void main(String[] args) { final ReadWriteLockTest readWriteLockTest = new ReadWriteLockTest(); Runnable task1 = new Runnable() { public void run() { readWriteLockTest.add(100); } }; Runnable task2 = new Runnable() { public void run() { readWriteLockTest.getValue(); } }; ExecutorService service = Executors.newCachedThreadPool(); for (int i=0;i<2;i++){ service.execute(task1); } for (int i=0;i<2;i++){ service.execute(task2); } for (int i=0;i<2;i++){ service.execute(task1); } service.shutdown(); } }
運行結(jié)果:
從這個結(jié)果我們很明顯的可以總結(jié)讀鎖和寫鎖
第一:如果執(zhí)行寫的時候,讀和寫必須等待
第二:如果執(zhí)行讀的時候,寫必須等待,而讀卻不用等待
也就是說讀和寫必須存在先后順序,不管是先讀還是先寫。
3:總結(jié)
相同點:lock能實現(xiàn)synchronized所有可以實現(xiàn)的
不同點:
1:lock不容易出現(xiàn)死鎖,而synchronized如果某個線程出現(xiàn)異常就會產(chǎn)生死鎖
2:lock更加靈活,可以通過tryLock來驗證是否獲取鎖,在線程中斷也同樣可以處理
3:lock有讀寫鎖在并發(fā)量大的時候具有很大的優(yōu)勢,因為讀的情況一般會比寫多很多