讀寫鎖
鎖機制引入了讀寫鎖特性:ReadWriteLock接口和唯一的實現類ReentrantReadWriteLock。讀寫鎖是鎖機制的最大改進之一,提供了將讀和寫分開處理的能力。ReentrantReadWriteLock有兩個鎖,一個是讀操作鎖,另一個是寫操作鎖。讀操作鎖允許多個線程同時訪問,但是寫操作鎖只允許一個線程進行訪問,在一個線程執行寫操作時,其他線程不能執行讀操作。在進行寫操作加鎖時,必須等待所有的讀操作鎖都釋放之後才能實施寫操作加鎖。
我們依然以計數器的例子來說明ReadWriteLock的用法,假設只有一個線程更新計數器的值,通過ReadWriteLock的writeLock方法取得寫操作鎖,進行寫操作的同步;其他線程都是讀取計數器的值用來顯示,通過ReadWriteLock的readLock方法取得讀操作鎖,進行讀操作的同步。示例代碼如下:
public class ReadWriteCountDemo {
public static void main(String[] args){
ReadWriteCounter counter = new ReadWriteCounter();
CountReader reader = new CountReader(counter);
CountWriter writer = new CountWriter(counter);
System.out.println("main:創建讀寫線程");
Thread r1 = new Thread(reader);
Thread r2 = new Thread(reader);
Thread r3 = new Thread(reader);
Thread w = new Thread(writer);
System.out.println("main:啓動讀寫線程");
w.start();
r1.start();
r2.start();
r3.start();
System.out.println("main:等待讀寫線程");
try {
w.join();
r1.join();
r2.join();
r3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main:退出");
}
}
class CountReader implements Runnable{
ReadWriteCounter counter = null;
CountReader(ReadWriteCounter counter){
this.counter = counter;
}
@Override
public void run() {
for(int i=0 ;i<5; i++){
long value = counter.get();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class CountWriter implements Runnable{
ReadWriteCounter counter = null;
CountWriter(ReadWriteCounter counter){
this.counter = counter;
}
@Override
public void run() {
for(int i=0; i<5; i++){
counter.increase();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class ReadWriteCounter{
private long count = 0;
private ReadWriteLock lock = new ReentrantReadWriteLock();
public void increase(){
System.out.println("寫操作:申請寫操作鎖");
lock.writeLock().lock();
System.out.println("寫操作:獲得寫操作鎖");
count++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println("寫操作:完成更新計數器。計數器值:" + count + ",釋放寫操作鎖");
lock.writeLock().unlock();
}
}
public long get(){
System.out.println(Thread.currentThread().getName() + ":讀操作:申請讀操作鎖");
lock.readLock().lock();
System.out.println(Thread.currentThread().getName() + ":讀操作:獲得讀操作鎖");
long tmp = count;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName() + ":讀操作:計數器值" + tmp + ",釋放讀操作鎖");
lock.readLock().unlock();
}
return tmp;
}
}
程序執行日誌:
main:創建讀寫線程
main:啓動讀寫線程
寫操作:申請寫操作鎖
寫操作:獲得寫操作鎖
main:等待讀寫線程
Thread-1:讀操作:申請讀操作鎖
Thread-2:讀操作:申請讀操作鎖
Thread-0:讀操作:申請讀操作鎖
寫操作:完成更新計數器。計數器值:1,釋放寫操作鎖
Thread-1:讀操作:獲得讀操作鎖
Thread-2:讀操作:獲得讀操作鎖
Thread-0:讀操作:獲得讀操作鎖
寫操作:申請寫操作鎖
Thread-0:讀操作:計數器值1,釋放讀操作鎖
Thread-2:讀操作:計數器值1,釋放讀操作鎖
Thread-1:讀操作:計數器值1,釋放讀操作鎖
寫操作:獲得寫操作鎖
Thread-0:讀操作:申請讀操作鎖
Thread-2:讀操作:申請讀操作鎖
Thread-1:讀操作:申請讀操作鎖
寫操作:完成更新計數器。計數器值:2,釋放寫操作鎖
Thread-0:讀操作:獲得讀操作鎖
Thread-2:讀操作:獲得讀操作鎖
Thread-1:讀操作:獲得讀操作鎖
寫操作:申請寫操作鎖
Thread-2:讀操作:計數器值2,釋放讀操作鎖
Thread-1:讀操作:計數器值2,釋放讀操作鎖
Thread-0:讀操作:計數器值2,釋放讀操作鎖
寫操作:獲得寫操作鎖
Thread-2:讀操作:申請讀操作鎖
Thread-0:讀操作:申請讀操作鎖
Thread-1:讀操作:申請讀操作鎖
寫操作:完成更新計數器。計數器值:3,釋放寫操作鎖
Thread-2:讀操作:獲得讀操作鎖
Thread-0:讀操作:獲得讀操作鎖
Thread-1:讀操作:獲得讀操作鎖
Thread-0:讀操作:計數器值3,釋放讀操作鎖
Thread-2:讀操作:計數器值3,釋放讀操作鎖
寫操作:申請寫操作鎖
Thread-1:讀操作:計數器值3,釋放讀操作鎖
寫操作:獲得寫操作鎖
Thread-2:讀操作:申請讀操作鎖
Thread-1:讀操作:申請讀操作鎖
Thread-0:讀操作:申請讀操作鎖
寫操作:完成更新計數器。計數器值:4,釋放寫操作鎖
Thread-2:讀操作:獲得讀操作鎖
Thread-1:讀操作:獲得讀操作鎖
Thread-0:讀操作:獲得讀操作鎖
Thread-2:讀操作:計數器值4,釋放讀操作鎖
寫操作:申請寫操作鎖
Thread-0:讀操作:計數器值4,釋放讀操作鎖
Thread-1:讀操作:計數器值4,釋放讀操作鎖
寫操作:獲得寫操作鎖
Thread-1:讀操作:申請讀操作鎖
Thread-2:讀操作:申請讀操作鎖
Thread-0:讀操作:申請讀操作鎖
寫操作:完成更新計數器。計數器值:5,釋放寫操作鎖
Thread-1:讀操作:獲得讀操作鎖
Thread-0:讀操作:獲得讀操作鎖
Thread-2:讀操作:獲得讀操作鎖
Thread-0:讀操作:計數器值5,釋放讀操作鎖
Thread-2:讀操作:計數器值5,釋放讀操作鎖
Thread-1:讀操作:計數器值5,釋放讀操作鎖
main:退出
可以看到:
- 進行寫操作加鎖時,必須等待所有的讀操作鎖釋放。
- 寫操作加鎖後,其他讀操作必須等待寫操作鎖釋放之後,才能再進行讀操作加鎖。