一、科普定義
這篇博文的兩個主角“synchronized”和“讀寫鎖”
1)synchronized
這個同步關鍵字相信大家都用得比較多,在上一篇“多個線程之間共享數據的方式”中也詳細列舉他的應用,在這就不多說只做幾點歸納:
- Java提供這個關鍵字,為防止資源沖突提供的內置支持。當任務執行到被synchronized保護的代碼片段的時候,它檢查鎖是否可用,然后獲取鎖,執行代碼,釋放鎖。
- 常用這個關鍵字可以修飾成員方法和代碼塊
2)讀寫鎖
我們對數據的操作無非兩種:“讀”和“寫”,試想一個這樣的情景,當十個線程同時讀取某個數據時,這個操作應不應該加同步。答案是沒必要的。只有以下兩種情況需要加同步:
- 這十個線程對這個公共數據既有讀又有寫
- 這十個線程對公共數據進行寫操作
- 以上兩點歸結起來就一點就是有對數據進行改變的操作就需要同步
所以
java5提供了讀寫鎖
用讀寫鎖實現
代碼分析:
這種鎖支持多線程讀操作不互斥,多線程讀寫互斥,多線程寫寫互斥。
讀操作不互斥這樣有助於性能的提高,這點在java5以前沒有
二.用一道面試題來具體比較這兩點
題目:“白板編程,實現一個緩存系統”
題目分析:
對這個緩存系統的理解:
間於用戶和數據庫中間的一個環節,我們知道用戶直接訪問數據庫的時間是遠大於直接訪問內存,所以有了緩存區后用戶訪問數據時 這樣,用戶先訪問緩存區當緩存區有用戶需要的數據時直接拿走,當緩存區沒有這樣的數據,訪問數據庫並把訪問所得的數據放在緩存區,這樣當下一個需要這個數據的用戶就直接訪問內存即可得到。
核心代碼實現:
首先用synchronized實現
- public synchronized Object getData(String key){
- Object result = map.get(key);
- if(result ==null){
- result = "new";//用這步代替訪問數據庫得數據
- }
- return result;
- }
- public synchronized Object getData(String key){
- Object result = map.get(key);
- if(result ==null){
- result = "new";//用這步代替訪問數據庫得數據
- }
- return result;
- }
- public Object getData(String key){
- rw.readLock().lock();//在讀前先上讀鎖
- Object result = null;
- try{
- result = map.get(key);
- //這個if比較關鍵,它避免了多余的幾次對數據哭的讀取
- if(result==null){
- //如果內存中沒有所要數據
- rw.readLock().unlock();
- rw.writeLock().lock();
- if(result==null){
- try{
- //我們用這個代替對數據庫訪問得到數據的步驟
- result = "new";
- }finally{
- rw.writeLock().unlock();
- }
- rw.readLock().lock();
- }
- }
- }finally{
- rw.readLock().unlock();
- }
- return result;
- }
- public Object getData(String key){
- rw.readLock().lock();//在讀前先上讀鎖
- Object result = null;
- try{
- result = map.get(key);
- //這個if比較關鍵,它避免了多余的幾次對數據哭的讀取
- if(result==null){
- //如果內存中沒有所要數據
- rw.readLock().unlock();
- rw.writeLock().lock();
- if(result==null){
- try{
- //我們用這個代替對數據庫訪問得到數據的步驟
- result = "new";
- }finally{
- rw.writeLock().unlock();
- }
- rw.readLock().lock();
- }
- }
- }finally{
- rw.readLock().unlock();
- }
- return result;
- }
- 用第一種方法處理,整個過程比較粗線條,代碼比較簡單單執行效率很低。這種方法的中心思想是不管你是什么操作,但凡涉及到公共資源就都給你同步。這么做可以是可以但是並不好。
- 第二種用讀寫鎖處理顯然是對前者的一個優化,對第二種方法做如下幾點說明:
- 關於unlock操作,我們知道只要是上了鎖就必須要解鎖,但是有這么一種情況就是當你上完鎖后在執行解鎖操作前程序出現異常,那這個所可能就一直存在。所以針對這個問題我們一般將unlock操作放在finally代碼塊中,就可以保證上了的鎖一定會被解。
- 上面的兩次if判斷,第一個if相信大家很好理解。但為什么要用第二個if呢?再假設一個場景,現在有十個線程來讀這個數據,而這個數據又不存在與緩存區,那么這十個線程中最先到的線程將執行“rw.writeLock().lock();”而另外九個線程將被阻塞,當第一個線程讀完以后緩存區實際上已經就有了這個數據,但另外九個阻塞在“rw.writeLock().lock();”如果不加這層if他們會繼續訪問數據庫,由此可見加了這層if對整個過程影響很大。這是比較細節的一點,就這一點Java的API文檔也考慮到了,它的樣例代碼如下:
- class CachedData {
- Object data;
- volatile boolean cacheValid;
- ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
- void processCachedData() {
- rwl.readLock().lock();
- <span style="color: rgb(255, 0, 0);">if (!cacheValid)</span> {
- // Must release read lock before acquiring write lock
- rwl.readLock().unlock();
- rwl.writeLock().lock();
- // Recheck state because another thread might have acquired
- // write lock and changed state before we did.
- <span style="color: rgb(255, 0, 0);"> if (!cacheValid)</span> {
- data = ...
- cacheValid = true;
- }
- // Downgrade by acquiring read lock before releasing write lock
- rwl.readLock().lock();
- rwl.writeLock().unlock(); // Unlock write, still hold read
- }
- use(data);
- rwl.readLock().unlock();
- }
- }
- class CachedData {
- Object data;
- volatile boolean cacheValid;
- ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
- void processCachedData() {
- rwl.readLock().lock();
- <span style="color:#ff00;">if (!cacheValid)</span> {
- // Must release read lock before acquiring write lock
- rwl.readLock().unlock();
- rwl.writeLock().lock();
- // Recheck state because another thread might have acquired
- // write lock and changed state before we did.
- <span style="color:#ff00;"> if (!cacheValid)</span> {
- data = ...
- cacheValid = true;
- }
- // Downgrade by acquiring read lock before releasing write lock
- rwl.readLock().lock();
- rwl.writeLock().unlock(); // Unlock write, still hold read
- }
- use(data);
- rwl.readLock().unlock();
- }
- }
學習在繼續!!!Go!Go!Go!!!