ReentrantReadWriteLock讀寫鎖的使用
類ReentrantLock具有完全互斥排他的效果,即同一時間只有一個執行緒在執行ReentrantLock.lock()後面的程式碼。這樣雖然保證了執行緒的安全性,但是效率低下。JDK提供了ReentrantReadWriteLock讀寫鎖,使用它可以加快效率,在某些不需要操作例項變數的方法中,完全可以使用讀寫鎖ReemtrantReadWriteLock來提升該方法的執行速度。
讀寫鎖表示有兩個鎖,一個是讀操作相關的鎖,也稱為共享鎖;另一個是寫操作相關的鎖,也叫排他鎖。也就是多個讀鎖之間不互斥,讀鎖與寫鎖互斥、寫鎖與寫鎖互斥。在沒有執行緒Thread進行寫入操作時,進行讀取操作的多個Thread都可以獲取讀鎖,而進行寫入操作的Thread只有在獲取寫鎖後才能進行寫入操作。即多個Thread可以同時進行讀取操作,但是同一時刻只允許一個Thread進行寫入操作。
1.讀讀共享
讀鎖與讀鎖可以共享,這種鎖一般用於只讀操作,不對變數進行修改操作。
package cn.qlq.thread.twelve; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import cn.qlq.thread.one.RunnableThread; public class Demo1 { private ReentrantReadWriteLock lock = newReentrantReadWriteLock();// 讀寫鎖 private static final Logger log = LoggerFactory.getLogger(Demo1.class); private int i; public String readI() { try { lock.readLock().lock();// 佔用讀鎖 log.info("threadName -> {} 佔用讀鎖,i->{}", Thread.currentThread().getName(), i); Thread.sleep(2 * 1000); } catch (InterruptedException e) { } finally { log.info("threadName -> {} 釋放讀鎖,i->{}", Thread.currentThread().getName(), i); lock.readLock().unlock();// 釋放讀鎖 } return i + ""; } public static void main(String[] args) { final Demo1 demo1 = new Demo1(); Runnable runnable = new Runnable() { @Override public void run() { demo1.readI(); } }; new Thread(runnable, "t1").start(); new Thread(runnable, "t2").start(); new Thread(runnable, "t3").start(); } }
結果:
18:27:20 [cn.qlq.thread.twelve.Demo1]-[INFO] threadName -> t2 佔用讀鎖,i->0
18:27:20 [cn.qlq.thread.twelve.Demo1]-[INFO] threadName -> t1 佔用讀鎖,i->0
18:27:20 [cn.qlq.thread.twelve.Demo1]-[INFO] threadName -> t3 佔用讀鎖,i->0
18:27:22 [cn.qlq.thread.twelve.Demo1]-[INFO] threadName -> t3 釋放讀鎖,i->0
18:27:22 [cn.qlq.thread.twelve.Demo1]-[INFO] threadName -> t1 釋放讀鎖,i->0
18:27:22 [cn.qlq.thread.twelve.Demo1]-[INFO] threadName -> t2 釋放讀鎖,i->0
2.寫寫互斥
寫鎖與寫鎖互斥,這就類似於ReentrantLock的作用效果。
package cn.qlq.thread.twelve; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Demo2 { private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();// 讀寫鎖 private static final Logger log = LoggerFactory.getLogger(Demo2.class); private int i; public void addI() { try { lock.writeLock().lock();// 佔用寫鎖 log.info("threadName -> {} 佔用寫鎖,i->{}", Thread.currentThread().getName(), i); Thread.sleep(2 * 1000); i++; } catch (InterruptedException e) { } finally { log.info("threadName -> {} 釋放寫鎖,i->{}", Thread.currentThread().getName(), i); lock.writeLock().unlock();// 釋放寫鎖 } } public static void main(String[] args) { final Demo2 demo1 = new Demo2(); Runnable runnable = new Runnable() { @Override public void run() { demo1.addI(); } }; new Thread(runnable, "t1").start(); new Thread(runnable, "t2").start(); new Thread(runnable, "t3").start(); } }
結果:(從時間可以看出實現了互斥效果)
18:31:31 [cn.qlq.thread.twelve.Demo2]-[INFO] threadName -> t1 佔用寫鎖,i->0
18:31:33 [cn.qlq.thread.twelve.Demo2]-[INFO] threadName -> t1 釋放寫鎖,i->1
18:31:33 [cn.qlq.thread.twelve.Demo2]-[INFO] threadName -> t2 佔用寫鎖,i->1
18:31:35 [cn.qlq.thread.twelve.Demo2]-[INFO] threadName -> t2 釋放寫鎖,i->2
18:31:35 [cn.qlq.thread.twelve.Demo2]-[INFO] threadName -> t3 佔用寫鎖,i->2
18:31:37 [cn.qlq.thread.twelve.Demo2]-[INFO] threadName -> t3 釋放寫鎖,i->3
3.讀寫互斥
package cn.qlq.thread.twelve; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 讀寫互斥 * * @author Administrator * */ public class Demo3 { private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();// 讀寫鎖 private static final Logger log = LoggerFactory.getLogger(Demo3.class); private int i; public String readI() { try { lock.readLock().lock();// 佔用讀鎖 log.info("threadName -> {} 佔用讀鎖,i->{}", Thread.currentThread().getName(), i); Thread.sleep(2 * 1000); } catch (InterruptedException e) { } finally { log.info("threadName -> {} 釋放讀鎖,i->{}", Thread.currentThread().getName(), i); lock.readLock().unlock();// 釋放讀鎖 } return i + ""; } public void addI() { try { lock.writeLock().lock();// 佔用寫鎖 log.info("threadName -> {} 佔用寫鎖,i->{}", Thread.currentThread().getName(), i); Thread.sleep(2 * 1000); i++; } catch (InterruptedException e) { } finally { log.info("threadName -> {} 釋放寫鎖,i->{}", Thread.currentThread().getName(), i); lock.writeLock().unlock();// 釋放寫鎖 } } public static void main(String[] args) throws InterruptedException { final Demo3 demo1 = new Demo3(); new Thread(new Runnable() { @Override public void run() { demo1.readI(); } }, "t1").start(); Thread.sleep(1 * 1000); new Thread(new Runnable() { @Override public void run() { demo1.addI(); } }, "t2").start(); } }
結果:
18:34:59 [cn.qlq.thread.twelve.Demo3]-[INFO] threadName -> t1 佔用讀鎖,i->0
18:35:01 [cn.qlq.thread.twelve.Demo3]-[INFO] threadName -> t1 釋放讀鎖,i->0
18:35:01 [cn.qlq.thread.twelve.Demo3]-[INFO] threadName -> t2 佔用寫鎖,i->0
18:35:03 [cn.qlq.thread.twelve.Demo3]-[INFO] threadName -> t2 釋放寫鎖,i->1
4.寫讀互斥
寫鎖與讀鎖也是互斥的。先佔用寫鎖後讀鎖進行搶佔也會等待寫鎖釋放。
package cn.qlq.thread.twelve; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * 讀寫互斥 * * @author Administrator * */ public class Demo3 { private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();// 讀寫鎖 private static final Logger log = LoggerFactory.getLogger(Demo3.class); private int i; public String readI() { try { lock.readLock().lock();// 佔用讀鎖 log.info("threadName -> {} 佔用讀鎖,i->{}", Thread.currentThread().getName(), i); Thread.sleep(2 * 1000); } catch (InterruptedException e) { } finally { log.info("threadName -> {} 釋放讀鎖,i->{}", Thread.currentThread().getName(), i); lock.readLock().unlock();// 釋放讀鎖 } return i + ""; } public void addI() { try { lock.writeLock().lock();// 佔用寫鎖 log.info("threadName -> {} 佔用寫鎖,i->{}", Thread.currentThread().getName(), i); Thread.sleep(2 * 1000); i++; } catch (InterruptedException e) { } finally { log.info("threadName -> {} 釋放寫鎖,i->{}", Thread.currentThread().getName(), i); lock.writeLock().unlock();// 釋放寫鎖 } } public static void main(String[] args) throws InterruptedException { final Demo3 demo1 = new Demo3(); new Thread(new Runnable() { @Override public void run() { demo1.addI(); } }, "t2").start(); Thread.sleep(1 * 1000); new Thread(new Runnable() { @Override public void run() { demo1.readI(); } }, "t1").start(); } }
結果:
18:36:14 [cn.qlq.thread.twelve.Demo3]-[INFO] threadName -> t2 佔用寫鎖,i->0
18:36:16 [cn.qlq.thread.twelve.Demo3]-[INFO] threadName -> t2 釋放寫鎖,i->1
18:36:16 [cn.qlq.thread.twelve.Demo3]-[INFO] threadName -> t1 佔用讀鎖,i->1
18:36:18 [cn.qlq.thread.twelve.Demo3]-[INFO] threadName -> t1 釋放讀鎖,i->1
總結: 讀寫、寫讀、寫寫都是互斥的,而讀讀是非同步非互斥的。
也就是隻要有寫鎖的參與就會進行同步,所以寫鎖也被稱為排他鎖,讀鎖被稱為共享鎖。