ReadWriteLock也是一個(gè)接口,提供了readLock和writeLock兩種鎖的操作機(jī)制,一個(gè)資源可以被多個(gè)線程同時(shí)讀,或者被一個(gè)線程寫(xiě),但是不能同時(shí)存在讀和寫(xiě)線程。
基本規(guī)則: 讀讀不互斥 讀寫(xiě)互斥 寫(xiě)寫(xiě)互斥
問(wèn)題: 既然讀讀不互斥,為何還要加讀鎖
答: 如果只是讀,是不需要加鎖的,加鎖本身就有性能上的損耗
如果讀可以不是最新數(shù)據(jù),也不需要加鎖
如果讀必須是最新數(shù)據(jù),必須加讀寫(xiě)鎖
讀寫(xiě)鎖相較于互斥鎖的優(yōu)點(diǎn)僅僅是允許讀讀的并發(fā),除此之外并無(wú)其他。
結(jié)論: 讀寫(xiě)鎖能夠保證讀取數(shù)據(jù)的 嚴(yán)格實(shí)時(shí)性,如果不需要這種 嚴(yán)格實(shí)時(shí)性,那么不需要加讀寫(xiě)鎖。
簡(jiǎn)單實(shí)現(xiàn):
package readandwrite; import java.util.Random;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.locks.ReentrantReadWriteLock; public class MyTest { private static ReentrantReadWriteLock rwl=new ReentrantReadWriteLock(); private static double data=0; static class readClass implements Runnable{ @Override public void run() { rwl.readLock().lock(); System.out.println("讀數(shù)據(jù):"+data); rwl.readLock().unlock(); } } static class writeClass implements Runnable{ private double i; public writeClass(double i) { this.i = i; } @Override public void run() { rwl.writeLock().lock(); data=i; System.out.println("寫(xiě)數(shù)據(jù): "+data); rwl.writeLock().unlock(); } } public static void main(String[] args) throws InterruptedException { ExecutorService pool=Executors.newCachedThreadPool(); for(int i=0;i<10;i++){ pool.submit(new readClass()); pool.submit(new writeClass((double)new Random().nextDouble())); pool.submit(new writeClass((double)new Random().nextDouble())); Thread.sleep(1000); } pool.shutdown(); } }
之前我們提到的鎖都是排它鎖(同一時(shí)刻只允許一個(gè)線程進(jìn)行訪問(wèn)),而讀寫(xiě)鎖維護(hù)了一對(duì)鎖,一個(gè)讀鎖,一個(gè)寫(xiě)鎖。讀寫(xiě)鎖在同一時(shí)刻允許多個(gè)線程進(jìn)行讀操作,但是寫(xiě)線程訪問(wèn)過(guò)程中,所有的讀線程和其他寫(xiě)線程均被阻塞。如此,并發(fā)性有了很大的提升。這樣,在某些讀遠(yuǎn)遠(yuǎn)大于寫(xiě)的場(chǎng)景中,讀寫(xiě)鎖能夠提供比排它鎖更好的并發(fā)量和吞吐量。
一個(gè)關(guān)于讀寫(xiě)鎖的Demo:
分析:設(shè)計(jì)一個(gè)模擬隊(duì)列,擁有一個(gè)data成員變量用于存儲(chǔ)數(shù)據(jù)和存取兩種操作。
import java.util.Random;import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantReadWriteLock;public class ReadWriteLockDemo{ public static void main(String[] args) { DefQueue queue = new DefQueue(); for (int i = 1; i < 10; i++) { //啟動(dòng)線程進(jìn)行讀操作 new Thread(new Runnable() { @Override public void run() { while (true) { queue.get(); } } }).start(); //啟動(dòng)線程進(jìn)行寫(xiě)操作 new Thread(new Runnable() { @Override public void run() { while(true) { queue.put(new Random().nextInt(10000)); } } }).start(); } }}class DefQueue{ private int data; ReadWriteLock rwLock = new ReentrantReadWriteLock(); public void get() { rwLock.readLock().lock();//加讀鎖 try { System.out.println(Thread.currentThread().getName() + "be ready to get data"); Thread.sleep((long) (Math.random() * 1000)); System.out.println(Thread.currentThread().getName() + "get the data: " + data); } catch (InterruptedException e) { e.printStackTrace(); } finally { rwLock.readLock().unlock();//釋放讀鎖 } } public void put(int data) { rwLock.writeLock().lock();//加寫(xiě)鎖 try { System.out.println(Thread.currentThread().getName() + " be ready to write data"); Thread.sleep((long) (Math.random() * 1000)); this.data = data; System.out.println(Thread.currentThread().getName() + " has wrote the data: "+data); } catch (InterruptedException e) { e.printStackTrace(); } finally { rwLock.writeLock().unlock();//釋放寫(xiě)鎖 } }}
程序部分運(yùn)行結(jié)果:
Thread-0be ready to get dataThread-0get the data: 0Thread-1 be ready to write dataThread-1 has wrote the data: 1156Thread-2be ready to get dataThread-2get the data: 1156Thread-3 be ready to write dataThread-3 has wrote the data: 9784Thread-3 be ready to write dataThread-3 has wrote the data: 4370Thread-3 be ready to write dataThread-3 has wrote the data: 1533Thread-4be ready to get dataThread-4get the data: 1533Thread-5 be ready to write dataThread-5 has wrote the data: 2345Thread-6be ready to get dataThread-6get the data: 2345Thread-9 be ready to write dataThread-9 has wrote the data: 9463Thread-9 be ready to write dataThread-9 has wrote the data: 9301Thread-9 be ready to write dataThread-9 has wrote the data: 549Thread-9 be ready to write dataThread-9 has wrote the data: 4673Thread-9 be ready to write data
我們可以看到打印語(yǔ)句結(jié)果很正常。
下面我們?cè)賮?lái)實(shí)現(xiàn)一個(gè)模擬緩沖區(qū)的小Demo:
import java.util.HashMap;import java.util.Map;import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantReadWriteLock;/* * @author vayne * * 多線程實(shí)現(xiàn)緩存的小demo */class Cachend{ volatile Map<String, String> cachmap = new HashMap<String, String>();//加volatile關(guān)鍵字保證可見(jiàn)性。 ReadWriteLock rwLock = new ReentrantReadWriteLock();//這個(gè)讀寫(xiě)鎖要定義在方法外面,使得每一個(gè)線程用的是同一個(gè)讀寫(xiě)鎖。 public String getS(String key) //如果定義在方法內(nèi)部,就是跟方法棧有關(guān)的讀寫(xiě)鎖。這樣可能不是同一個(gè)鎖。 { rwLock.readLock().lock(); String value = null; try { value = cachmap.get(key); if (cachmap.get(key) == null)//這里要重新獲得key對(duì)應(yīng)的value值 { rwLock.readLock().unlock(); rwLock.writeLock().lock(); try { if (cachmap.get(key) == null)//這里也是 { value = "" + Thread.currentThread().getName(); cachmap.put(key, value); System.out.println(Thread.currentThread().getName() + " put the value ::::" + value); } } finally { rwLock.readLock().lock(); //將鎖降級(jí),這里跟下一句的順序不能反。 rwLock.writeLock().unlock();//關(guān)于這里的順序問(wèn)題,下面我會(huì)提到。 } } } finally { rwLock.readLock().unlock(); } return cachmap.get(key); }}public class CachendDemo{ public static void main(String[] args) { Cachend ca = new Cachend(); for (int i = 0; i < 4; i++) { new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+" "+ca.getS("demo1")); System.out.println(Thread.currentThread().getName()+" "+ca.cachmap.entrySet()); } }).start(); } }}
運(yùn)行結(jié)果:
Thread-0 put the value ::::Thread-0Thread-0 Thread-0Thread-0 [demo1=Thread-0]Thread-2 Thread-0Thread-2 [demo1=Thread-0]Thread-3 Thread-0Thread-3 [demo1=Thread-0]Thread-1 Thread-0Thread-1 [demo1=Thread-0]
上面我給出了一些注釋?zhuān)鋵?shí)這個(gè)代碼是很不好寫(xiě)的,考慮的東西很多。下面我來(lái)講一下上面的代碼中提到的順序問(wèn)題。
對(duì)于讀寫(xiě)鎖我們應(yīng)該了解下面的一些性質(zhì)(這些性質(zhì)是由源代碼得出來(lái)的,因?yàn)樵创a的設(shè)計(jì),所以才有下列性質(zhì)):
第二條對(duì)應(yīng)我們上面的程序就是,如果我們添加了“demo1”對(duì)應(yīng)的value值,然后釋放了寫(xiě)鎖,此時(shí)在當(dāng)前線程S還未獲得讀鎖時(shí),另一個(gè)線程T又獲得了寫(xiě)鎖,那么就會(huì)將S的操作給覆蓋(如果取到的值已經(jīng)緩存在S中,那么T的操作就無(wú)法被S感知了,到最后依然會(huì)返回S操作的值)。
再來(lái)看一個(gè)DEMO:
讀寫(xiě)鎖,分為讀鎖和寫(xiě)鎖,多個(gè)讀鎖不互斥,讀鎖和寫(xiě)鎖互斥,寫(xiě)鎖與寫(xiě)鎖互斥,這是JVM自己控制的,你只要上好相應(yīng)的鎖即可,如果你的代碼只讀數(shù)據(jù),可以很多人同時(shí)讀,但不能同時(shí)寫(xiě),那就上讀鎖;如果你的代碼修改數(shù)據(jù),只能有一個(gè)人在寫(xiě),且不能同時(shí)讀取,那就上寫(xiě)鎖.總之,讀的時(shí)候上讀鎖,寫(xiě)的時(shí)候上寫(xiě)鎖!
看如下程序: 新建6個(gè)線程,3個(gè)線程用來(lái)讀,3個(gè)線程用來(lái)寫(xiě),
package javaplay.thread.test; import java.util.Random;import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantReadWriteLock; public class ReadWriteLockTest { public static void main(String[] args) { final Queue3 q3 = new Queue3(); for (int i = 0; i < 3; i++) { new Thread() { public void run() { while (true) { q3.get(); } } }.start(); new Thread() { public void run() { while (true) { q3.put(new Random().nextInt(10000)); } } }.start(); } }} class Queue3 { private Object data = null;// 共享數(shù)據(jù),只能有一個(gè)線程能寫(xiě)該數(shù)據(jù),但可以有多個(gè)線程同時(shí)讀該數(shù)據(jù)。 // 讀寫(xiě)鎖 ReadWriteLock rwl = new ReentrantReadWriteLock(); // 相當(dāng)于讀操作 public void get() { rwl.readLock().lock(); try { System.out.println(Thread.currentThread().getName() + " be ready to read data!"); Thread.sleep((long) (Math.random() * 1000)); System.out.println(Thread.currentThread().getName() + "have read data :" + data); } catch (InterruptedException e) { e.printStackTrace(); } finally { rwl.readLock().unlock(); } } // 相當(dāng)于寫(xiě)操作 public void put(Object data) { rwl.writeLock().lock(); try { System.out.println(Thread.currentThread().getName() + " be ready to write data!"); Thread.sleep((long) (Math.random() * 1000)); this.data = data; System.out.println(Thread.currentThread().getName() + " have write data: " + data); } catch (InterruptedException e) { e.printStackTrace(); } finally { rwl.writeLock().unlock(); } }}
讀寫(xiě)鎖功能很強(qiáng)大!這樣可以實(shí)現(xiàn)正常的邏輯,如果我們把讀寫(xiě)鎖相關(guān)的代碼注釋,發(fā)現(xiàn)程序正準(zhǔn)備寫(xiě)的時(shí)候,就有線程讀了,發(fā)現(xiàn)準(zhǔn)備讀的時(shí)候,有線程去寫(xiě),這樣不符合我們的邏輯;通過(guò)Java5的新特新可以很輕松的解決這樣的問(wèn)題;
查看Java API ReentrantReadWriteLock 上面有經(jīng)典(緩存)的用法,下面是doc里面的偽代碼,,它演示的是一個(gè)實(shí)體的緩存,不是緩存系統(tǒng),相當(dāng)于緩存代理,注意volatile的運(yùn)用:
package javaplay.thread.test; import java.util.concurrent.locks.ReentrantReadWriteLock; /* * Sample usages. Here is a code sketch showing how to perform lock downgrading after updating a cache * (exception handling is particularly tricky when handling multiple locks in a non-nested fashion): */class CachedData { Object data; volatile boolean cacheValid; final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); void processCachedData() { rwl.readLock().lock(); if (!cacheValid) { // Must release read lock before acquiring write lock rwl.readLock().unlock(); rwl.writeLock().lock(); try { // Recheck state because another thread might have // acquired write lock and changed state before we did. if (!cacheValid) { data = ... cacheValid = true; } // Downgrade by acquiring read lock before releasing write lock rwl.readLock().lock(); } finally { rwl.writeLock().unlock(); // Unlock write, still hold read } } try { use(data); } finally { rwl.readLock().unlock(); } }}
假設(shè)現(xiàn)在多個(gè)線程來(lái)讀了,那第一個(gè)線程讀到的數(shù)據(jù)是空的,那它就要寫(xiě)就要填充數(shù)據(jù),那么第二個(gè)第三個(gè)就應(yīng)該互斥等著,一進(jìn)來(lái)是來(lái)讀數(shù)據(jù)的所以上讀鎖,進(jìn)來(lái)后發(fā)現(xiàn)數(shù)據(jù)是空的,就先把讀鎖釋放再重新獲取寫(xiě)鎖,就開(kāi)始寫(xiě)數(shù)據(jù),數(shù)據(jù)寫(xiě)完了,就把寫(xiě)鎖釋放,把讀鎖重新掛上,持有讀鎖時(shí)不能同時(shí)獲取寫(xiě)鎖,但擁有寫(xiě)鎖時(shí)可同時(shí)再獲取讀鎖,自己線程掛的寫(xiě)鎖可同時(shí)掛讀鎖的,這就是降級(jí),就是除了讀鎖和寫(xiě)鎖外,還有讀寫(xiě)鎖也叫更新鎖,就是自己即可以讀又可以寫(xiě)的鎖,也就是在自己擁有寫(xiě)鎖還沒(méi)釋放寫(xiě)鎖時(shí)就獲取了讀鎖就降級(jí)為讀寫(xiě)鎖/更新鎖,但是不能在持有讀鎖時(shí)再獲取寫(xiě)鎖;
基于上面的例子,我們可以實(shí)現(xiàn)一個(gè)緩存系統(tǒng):
package javaplay.thread.test; import java.util.HashMap;import java.util.Map;import java.util.concurrent.locks.ReadWriteLock;import java.util.concurrent.locks.ReentrantReadWriteLock; public class CacheDemo { private Map<String, Object> cache = new HashMap<>(); public static void main(String[] args) { } // 可做到多個(gè)線程并必的讀 讀和寫(xiě)又互斥 系統(tǒng)性能很高 // 這就是讀寫(xiě)鎖的價(jià)值 private ReadWriteLock rwl = new ReentrantReadWriteLock(); public Object getData(String key) { rwl.readLock().lock(); Object value = null; try { value = cache.get(key); if (value == null) {// 避免首次多次查詢要加synchronized rwl.readLock().unlock(); rwl.writeLock().lock(); try { if (value == null) // 就算第二個(gè)第三個(gè)線程進(jìn)來(lái)時(shí)也不用再寫(xiě)了 跟偽代碼相同原理 value = "aaa";// 實(shí)際去query db } finally { rwl.writeLock().unlock(); } rwl.readLock().lock(); } } finally { rwl.readLock().unlock(); } return value; }}錯(cuò)誤之處:沒(méi)有把不存在的值put;要用get(key)來(lái)判空
感謝大家對(duì)VeVb武林網(wǎng)的支持。
新聞熱點(diǎn)
疑難解答
圖片精選