說到Java并發(fā)編程缨叫,很多開發(fā)第一個想到同時也是經(jīng)常常用的肯定是Synchronized,但是小編這里提出一個問題钱床,Synchronized存在明顯的一個性能問題就是讀與讀之間互斥吼畏,簡言之就是,我們編程想要實現(xiàn)的最好效果是蛤签,可以做到讀和讀互不影響,讀和寫互斥栅哀,寫和寫互斥震肮,提高讀寫的效率,如何實現(xiàn)呢留拾?
Java并發(fā)包中ReadWriteLock是一個接口戳晌,主要有兩個方法,如下:
public interface ReadWriteLock {
/**
* Returns the lock used for reading.
*
* @return the lock used for reading
*/
Lock readLock();
/**
* Returns the lock used for writing.
*
* @return the lock used for writing
*/
Lock writeLock();
}
ReadWriteLock管理一組鎖痴柔,一個是只讀的鎖钳垮,一個是寫鎖惜姐。
Java并發(fā)庫中ReetrantReadWriteLock實現(xiàn)了ReadWriteLock接口并添加了可重入的特性病蛉。
在具體講解ReetrantReadWriteLock的使用方法前汽烦,我們有必要先對其幾個特性進行一些深入學習了解鸿捧。
1. ReetrantReadWriteLock特性說明
1.1 獲取鎖順序
- 非公平模式(默認)
當以非公平初始化時屹篓,讀鎖和寫鎖的獲取的順序是不確定的。非公平鎖主張競爭獲取匙奴,可能會延緩一個或多個讀或寫線程堆巧,但是會比公平鎖有更高的吞吐量。 - 公平模式
當以公平模式初始化時泼菌,線程將會以隊列的順序獲取鎖谍肤。當當前線程釋放鎖后,等待時間最長的寫鎖線程就會被分配寫鎖哗伯;或者有一組讀線程組等待時間比寫線程長荒揣,那么這組讀線程組將會被分配讀鎖。
1.2 可重入
什么是可重入鎖系任,不可重入鎖呢恳蹲?"重入"字面意思已經(jīng)很明顯了,就是可以重新進入俩滥〖卫伲可重入鎖,就是說一個線程在獲取某個鎖后霜旧,還可以繼續(xù)獲取該鎖错忱,即允許一個線程多次獲取同一個鎖。比如synchronized內置鎖就是可重入的挂据,如果A類有2個synchornized方法method1和method2以清,那么method1調用method2是允許的。顯然重入鎖給編程帶來了極大的方便崎逃。假如內置鎖不是可重入的玖媚,那么導致的問題是:1個類的synchornized方法不能調用本類其他synchornized方法,也不能調用父類中的synchornized方法婚脱。與內置鎖對應今魔,JDK提供的顯示鎖ReentrantLock也是可以重入的,這里通過一個例子著重說下可重入鎖的釋放需要的事兒障贸。
package test;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Test1 {
public static void main(String[] args) throws InterruptedException {
final ReentrantReadWriteLock lock = new ReentrantReadWriteLock ();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
lock.writeLock().lock();
System.out.println("Thread real execute");
lock.writeLock().unlock();
}
});
lock.writeLock().lock();
lock.writeLock().lock();
t.start();
Thread.sleep(200);
System.out.println("realse one once");
lock.writeLock().unlock();
}
}
從運行結果中错森,可以看到,程序并未執(zhí)行線程的run方法篮洁,由此我們可知涩维,上面的代碼會出現(xiàn)死鎖,因為主線程2次獲取了鎖袁波,但是卻只釋放1次鎖瓦阐,導致線程t永遠也不能獲取鎖。一個線程獲取多少次鎖篷牌,就必須釋放多少次鎖睡蟋。這對于內置鎖也是適用的,每一次進入和離開synchornized方法(代碼塊)枷颊,就是一次完整的鎖獲取和釋放戳杀。
1.3 鎖降級
要實現(xiàn)一個讀寫鎖,需要考慮很多細節(jié)夭苗,其中之一就是鎖升級和鎖降級的問題信卡。什么是升級和降級呢?ReadWriteLock的javadoc有一段話:
Can the write lock be downgraded to a read lock without allowing an intervening writer? Can a read lock be upgraded to a write lock, in preference to other waiting readers or writers?
翻譯過來的結果是:在不允許中間寫入的情況下题造,寫入鎖可以降級為讀鎖嗎傍菇?讀鎖是否可以升級為寫鎖,優(yōu)先于其他等待的讀取或寫入操作界赔?簡言之就是說丢习,鎖降級:從寫鎖變成讀鎖须妻;鎖升級:從讀鎖變成寫鎖,ReadWriteLock是否支持呢泛领?讓我們帶著疑問荒吏,進行一些Demo 測試代碼驗證。
Test Code 1
/**
*Test Code 1
**/
package test;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Test1 {
public static void main(String[] args) {
ReentrantReadWriteLock rtLock = new ReentrantReadWriteLock();
rtLock.readLock().lock();
System.out.println("get readLock.");
rtLock.writeLock().lock();
System.out.println("blocking");
}
}
Test Code 1 Result
結論:上面的測試代碼會產(chǎn)生死鎖渊鞋,因為同一個線程中绰更,在沒有釋放讀鎖的情況下,就去申請寫鎖锡宋,這屬于鎖升級儡湾,ReentrantReadWriteLock是不支持的。
Test Code 2
/**
*Test Code 2
**/
package test;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Test2 {
public static void main(String[] args) {
ReentrantReadWriteLock rtLock = new ReentrantReadWriteLock();
rtLock.writeLock().lock();
System.out.println("writeLock");
rtLock.readLock().lock();
System.out.println("get read lock");
}
}
Test Code 2 Result
結論:ReentrantReadWriteLock支持鎖降級执俩,上面代碼不會產(chǎn)生死鎖徐钠。這段代碼雖然不會導致死鎖,但沒有正確的釋放鎖役首。從寫鎖降級成讀鎖尝丐,并不會自動釋放當前線程獲取的寫鎖,仍然需要顯示的釋放衡奥,否則別的線程永遠也獲取不到寫鎖爹袁。
2. ReetrantReadWriteLock對比使用
2.1 Synchronized實現(xiàn)
在使用ReetrantReadWriteLock實現(xiàn)鎖機制前,我們先看一下矮固,多線程同時讀取文件時失息,用synchronized實現(xiàn)的效果
package test;
/**
*
* synchronized實現(xiàn)
* @author itbird
*
*/
public class ReadAndWriteLockTest {
public synchronized static void get(Thread thread) {
System.out.println("start time:" + System.currentTimeMillis());
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread.getName() + ":正在進行讀操作……");
}
System.out.println(thread.getName() + ":讀操作完畢!");
System.out.println("end time:" + System.currentTimeMillis());
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
get(Thread.currentThread());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
get(Thread.currentThread());
}
}).start();
}
}
讓我們看一下運行結果:
從運行結果可以看出档址,兩個線程的讀操作是順序執(zhí)行的盹兢,整個過程大概耗時200ms。
2.2 ReetrantReadWriteLock實現(xiàn)
package test;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
*
* ReetrantReadWriteLock實現(xiàn)
* @author itbird
*
*/
public class ReadAndWriteLockTest {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public static void get(Thread thread) {
lock.readLock().lock();
System.out.println("start time:" + System.currentTimeMillis());
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread.getName() + ":正在進行讀操作……");
}
System.out.println(thread.getName() + ":讀操作完畢守伸!");
System.out.println("end time:" + System.currentTimeMillis());
lock.readLock().unlock();
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
get(Thread.currentThread());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
get(Thread.currentThread());
}
}).start();
}
}
讓我們看一下運行結果:
從運行結果可以看出绎秒,兩個線程的讀操作是同時執(zhí)行的,整個過程大概耗時100ms含友。
通過兩次實驗的對比替裆,我們可以看出來校辩,ReetrantReadWriteLock的效率明顯高于Synchronized關鍵字窘问。
3. ReetrantReadWriteLock讀寫鎖互斥關系
通過上面的測試代碼,我們也可以延伸得出一個結論宜咒,ReetrantReadWriteLock讀鎖使用共享模式惠赫,即:同時可以有多個線程并發(fā)地讀數(shù)據(jù)。但是另一個問題來了故黑,寫鎖之間是共享模式還是互斥模式儿咱?讀寫鎖之間是共享模式還是互斥模式呢庭砍?下面讓我們通過Demo進行一一驗證吧。
3.1 ReetrantReadWriteLock讀寫鎖關系
package test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
*
* ReetrantReadWriteLock實現(xiàn)
* @author itbird
*
*/
public class ReadAndWriteLockTest {
public static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public static void main(String[] args) {
//同時讀混埠、寫
ExecutorService service = Executors.newCachedThreadPool();
service.execute(new Runnable() {
@Override
public void run() {
readFile(Thread.currentThread());
}
});
service.execute(new Runnable() {
@Override
public void run() {
writeFile(Thread.currentThread());
}
});
}
// 讀操作
public static void readFile(Thread thread) {
lock.readLock().lock();
boolean readLock = lock.isWriteLocked();
if (!readLock) {
System.out.println("當前為讀鎖怠缸!");
}
try {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread.getName() + ":正在進行讀操作……");
}
System.out.println(thread.getName() + ":讀操作完畢!");
} finally {
System.out.println("釋放讀鎖钳宪!");
lock.readLock().unlock();
}
}
// 寫操作
public static void writeFile(Thread thread) {
lock.writeLock().lock();
boolean writeLock = lock.isWriteLocked();
if (writeLock) {
System.out.println("當前為寫鎖揭北!");
}
try {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread.getName() + ":正在進行寫操作……");
}
System.out.println(thread.getName() + ":寫操作完畢!");
} finally {
System.out.println("釋放寫鎖吏颖!");
lock.writeLock().unlock();
}
}
}
運行結果:
結論:讀寫鎖的實現(xiàn)必須確保寫操作對讀操作的內存影響搔体。換句話說,一個獲得了讀鎖的線程必須能看到前一個釋放的寫鎖所更新的內容半醉,讀寫鎖之間為互斥疚俱。
3.2 ReetrantReadWriteLock寫鎖關系
package test;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
*
* ReetrantReadWriteLock實現(xiàn)
* @author itbird
*
*/
public class ReadAndWriteLockTest {
public static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public static void main(String[] args) {
//同時寫
ExecutorService service = Executors.newCachedThreadPool();
service.execute(new Runnable() {
@Override
public void run() {
writeFile(Thread.currentThread());
}
});
service.execute(new Runnable() {
@Override
public void run() {
writeFile(Thread.currentThread());
}
});
}
// 讀操作
public static void readFile(Thread thread) {
lock.readLock().lock();
boolean readLock = lock.isWriteLocked();
if (!readLock) {
System.out.println("當前為讀鎖!");
}
try {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread.getName() + ":正在進行讀操作……");
}
System.out.println(thread.getName() + ":讀操作完畢缩多!");
} finally {
System.out.println("釋放讀鎖呆奕!");
lock.readLock().unlock();
}
}
// 寫操作
public static void writeFile(Thread thread) {
lock.writeLock().lock();
boolean writeLock = lock.isWriteLocked();
if (writeLock) {
System.out.println("當前為寫鎖!");
}
try {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(thread.getName() + ":正在進行寫操作……");
}
System.out.println(thread.getName() + ":寫操作完畢衬吆!");
} finally {
System.out.println("釋放寫鎖登馒!");
lock.writeLock().unlock();
}
}
}
運行結果:
4. 總結
1.Java并發(fā)庫中ReetrantReadWriteLock實現(xiàn)了ReadWriteLock接口并添加了可重入的特性
2.ReetrantReadWriteLock讀寫鎖的效率明顯高于synchronized關鍵字
3.ReetrantReadWriteLock讀寫鎖的實現(xiàn)中,讀鎖使用共享模式咆槽;寫鎖使用獨占模式陈轿,換句話說,讀鎖可以在沒有寫鎖的時候被多個線程同時持有秦忿,寫鎖是獨占的
4.ReetrantReadWriteLock讀寫鎖的實現(xiàn)中麦射,需要注意的,當有讀鎖時灯谣,寫鎖就不能獲得潜秋;而當有寫鎖時,除了獲得寫鎖的這個線程可以獲得讀鎖外胎许,其他線程不能獲得讀鎖
看完了使用方法峻呛,各位看官是否對于ReetrantReadWriteLock的實現(xiàn)原理有一探究竟的沖動呢?