synchronized內部鎖與ReentrantLock鎖都是獨占鎖(排它鎖), 同一時間只允許一個線程執行同步代碼塊,可以保證線程的安全性,但是執行效率低。
ReentrantReadWriteLock讀寫鎖是一種改進的排他鎖,也可以稱作共享/排他鎖. 允許多個線程同時讀取共享數據,但是一次只允許一個線程對共享數據進行更新。
讀寫鎖通過讀鎖與寫鎖來完成讀寫操作. 線程在讀取共享數據前必須先持有讀鎖,該讀鎖可以同時被多個線程持有,即它是共享的.線程在修改共享數據前必須先持有寫鎖,寫鎖是排他的, 一個線程持有寫鎖時其他線程無法獲得相應的鎖。
讀鎖只是在讀線程之間共享,任何一個線程持有讀鎖時,其他線程都無法獲得寫鎖, 保證線程在讀取數據期間沒有其他線程對數據進行更新,使得讀線程能夠讀到數據的最新值,保證在讀數據期間共享變量不被修改。
獲得條件 | 排他性 | 作用 | |
---|---|---|---|
讀鎖 | 寫鎖未被任意線程持有 | 對讀線程是共享的,對寫線程是排他的 | 允許多個讀線程可以同時讀取共享數據,保證在讀共享數據時,沒有其他線程對共享數據進行修改 |
寫鎖 | 該寫鎖未被其他線程持有,并且相應的讀鎖也未被其他線程持有 | 對讀線程或者寫線程都是排他的 | 保證寫線程以獨占的方式修改共享數據 |
讀寫鎖允許讀讀共享, 讀寫互斥,寫寫互斥。
在java.util.concurrent.locks包中定義了ReadWriteLock接口,該接口中定義了readLock()返回讀鎖,定義writeLock()方法返回寫鎖. 該接口的實現類是ReentrantReadWriteLock。
注意readLock()與writeLock()方法返回的鎖對象是同一個鎖的兩個不同的角色, 不是分別獲得兩個不同的鎖. ReadWriteLock接口實例可以充當兩個角色.讀寫鎖的其他使用方法。
//定義讀寫鎖
ReadWriteLock rwLock = new ReentrantReadWriteLock();
//獲得讀鎖
Lock readLock = rwLock.readLock();
//獲得寫鎖
Lock writeLock = rwLock.writeLock();
//讀數據
readLock.lock(); //申請讀鎖
try{
讀取共享數據
}finally{
readLock.unlock(); //總是在finally子句中釋放鎖
}
//寫數據
writeLock.lock(); //申請寫鎖
try{
更新修改共享數據
}finally{
writeLock.unlock(); //總是在finally子句中釋放鎖
}
讀讀共享
ReadWriteLock讀寫鎖可以實現多個線程同時讀取共享數據,即讀讀共享,可以提高程序的讀取數據的效率。
package com.wkcto.lock.readwrite;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* ReadWriteLock讀寫鎖可以實現讀讀共享,允許多個線程同時獲得讀鎖
*/
public class Test01 {
static class Service{
//定義讀寫鎖
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//定義方法讀取數據
public void read(){
try {
readWriteLock.readLock().lock(); //獲得讀鎖
System.out.println(Thread.currentThread().getName() + "獲得讀鎖,開始讀取數據的時間--" + System.currentTimeMillis());
TimeUnit.SECONDS.sleep(3); //模擬讀取數據用時
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
readWriteLock.readLock().unlock(); //釋放讀鎖
}
}
}
public static void main(String[] args) {
Service service = new Service();
//創建5個線程,調用read()方法
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
service.read(); //在線程中調用read()讀取數據
}
}).start();
}
//運行程序后,這多個 線程幾乎可以同時獲得鎖讀,執行lock()后面的代碼
}
}
寫寫互斥
通過ReadWriteLock讀寫鎖中的寫鎖,只允許有一個線程執行lock()后面的代碼。
package com.wkcto.lock.readwrite;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 演示ReadWriteLock的writeLock()寫鎖是互斥的,只允許有一個線程持有
*/
public class Test02 {
static class Service{
//先定義讀寫鎖
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//定義方法修改數據
public void write(){
try {
readWriteLock.writeLock().lock(); //申請獲得寫鎖
System.out.println(Thread.currentThread().getName() + "獲得寫鎖,開始修改數據的時間--" + System.currentTimeMillis());
Thread.sleep(3000); //模擬修改數據的用時
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName() + "讀取數據完畢時的時間==" + System.currentTimeMillis());
readWriteLock.writeLock().unlock(); //釋放寫鎖
}
}
}
public static void main(String[] args) {
Service service = new Service();
//創建5個線程修改數據
for (int i = 0; i < 5; i++) {
new Thread(new Runnable() {
@Override
public void run() {
service.write(); //調用修改數據的方法
}
}).start();
}
//從執行結果來看,同一時間只有一個線程獲得寫鎖
}
}
讀寫互斥
寫鎖是獨占鎖,是排他鎖,讀線程與寫線程也是互斥的。
package com.wkcto.lock.readwrite;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 演示ReadWriteLock的讀寫互斥
* 一個線程獲得讀鎖時,寫線程等待; 一個線程獲得寫鎖時,其他線程等待
*/
public class Test03 {
static class Service{
//先定義讀寫鎖
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
Lock readLock = readWriteLock.readLock(); //獲得讀鎖
Lock writeLock = readWriteLock.writeLock(); //獲得寫鎖
//定義方法讀取數據
public void read(){
try {
readLock.lock(); //申請獲得讀鎖
System.out.println(Thread.currentThread().getName() + "獲得讀鎖,開始讀取數據的時間--" + System.currentTimeMillis());
Thread.sleep(3000); //模擬讀取數據的用時
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName() + "讀取數據完畢時的時間==" + System.currentTimeMillis());
readLock.unlock(); //釋放讀鎖
}
}
//定義方法修改數據
public void write(){
try {
writeLock.lock(); //申請獲得寫鎖
System.out.println(Thread.currentThread().getName() + "獲得寫鎖,開始修改數據的時間--" + System.currentTimeMillis());
Thread.sleep(3000); //模擬修改數據的用時
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName() + "修改數據完畢時的時間==" + System.currentTimeMillis());
writeLock.unlock(); //釋放寫鎖
}
}
}
public static void main(String[] args) {
Service service = new Service();
//定義一個線程讀數據
new Thread(new Runnable() {
@Override
public void run() {
service.read();
}
}).start();
//定義一個線程寫數據
new Thread(new Runnable() {
@Override
public void run() {
service.write();
}
}).start();
}
}