本文內容
何時該使用讀寫鎖.
讀寫鎖的寫法.
理解讀寫鎖和線程互斥的區別。
復習-同步化的概念
當一個方法或代碼塊被聲明成synchronized,要執行此代碼必須先取得一個對象實例或this的鎖定,這個鎖定要在synchronized修飾的方法或代碼塊執行完后才能釋放掉(無論這段代碼是怎樣返回的,是正常運行還是異常運行)。每個對象只有一個鎖定,如果有兩個不同的線程試圖同時調用同一對象的同步方法,最終只會有一個能運行此方法,另外一個要等待第一個線程釋放掉鎖定后才能運行此方法。
讀寫鎖應用的場合
我們有時會遇到對同一個內存區域如數組或者鏈表進行多線程讀寫的情況,一般來說有以下幾種處理方式: 1.不加任何限制,多見于讀取寫入都很快的情況,但有時也會出現問題. 2.對讀寫函數都加以同步互斥,這下問題是沒了,但效率也下去了,比如說兩個讀取線程不是非要排隊進入不可. 3.使用讀寫鎖,安全和效率都得到了解決,特別合適讀線程多于寫線程的情況.也就是下面將要展現的模式.
讀寫鎖的意圖
讀寫鎖的本意是分別對讀寫狀態進行互斥區分,有互斥時才加鎖,否則放行.互斥的情況有: 1.讀寫互斥. 2.寫寫互斥. 不互斥的情況是:讀讀,這種情況不該加以限制. 程序就是要讓鎖對象知道當前讀寫狀態,再根據情況對讀寫的線程進行鎖定和解鎖。
讀寫線程都要操作的數據類
讀寫線程都要操作的數據是鏈表datas。
注意其中try...finally 的寫法,它保證了加鎖解鎖過程是成對調用的
lpublic class DataLib {
private List<String> datas;
private ReadWriteLock lock;
public DataLib() {
datas = new ArrayList<String>();
lock = new ReadWriteLock();
}
// 寫入數據,這時不能讀取
public void writeData(List<String> newDatas) {
try {
lock.writeLock();
Test.sleep(2);
datas=newDatas;
} finally {
lock.writeUnlock();
}
}
// 讀取數據,這時不能寫入
public List<String> readData() {
try {
lock.readLock();
Test.sleep(1);
return datas;
} finally {
lock.readUnlock();
}
}
}
讀寫鎖ReadWriteLock類
public class ReadWriteLock{
// 讀狀態
private boolean isRead;
// 寫狀態
private boolean isWrite;
public synchronized void readLock(){
// 有寫入時讀取線程停止
while(isWrite){
try{
System.out.println("有線程在進行寫入,讀取線程停止,進入等待狀態");
wait();
}
catch(InterruptedException ex){
ex.printStackTrace();
}
}
System.out.println("設定鎖為讀取狀態");
isRead=true;
}
public synchronized void readUnlock(){
System.out.println("解除讀取鎖");
isRead=false;
notifyAll();
}
public synchronized void writeLock(){
// 有讀取時讀取線程停止
while(isRead){
try{
System.out.println("有線程在進行讀取,寫入線程停止,進入等待狀態");
wait();
}
catch(InterruptedException ex){
ex.printStackTrace();
}
}
// 有寫入時寫入線程也一樣要停止
while(isWrite){
try{
System.out.println("有線程在進行寫入,寫入線程停止,進入等待狀態");
wait();
}
catch(InterruptedException ex){
ex.printStackTrace();
}
}
System.out.println("設定鎖為寫入狀態");
isWrite=true;
}
public synchronized void writeUnlock(){
System.out.println("解除寫入鎖");
isWrite=false;
notifyAll();
}
}
寫線程類Writer -它用于往DataLib類實例中的datas字段寫數據
分析其中dataLib字段的用意。
注意并記住其中持續調用及使用隨機數的方法。
lpublic class Writer implements Runnable{
private DataLib dataLib;
private static final Random random=new Random();
private String[] mockDatas={"甲","乙","丙","丁","戊","己","庚","辛","壬","癸"};
public Writer(DataLib dataLib,String[] mockDatas){
this.dataLib=dataLib;
this.mockDatas=mockDatas;
Thread thread=new Thread(this);
thread.start();
}
public void run(){
while(true){
Test.sleep(random.nextInt(3));
int startIndex=random.nextInt(mockDatas.length);
ArrayList<String> newDatas=new ArrayList<String>();
for(int i=startIndex;i<mockDatas.length;i++){
newDatas.add(mockDatas[i]);
}
dataLib.writeData(newDatas);
}
}
}
讀線程類Reader -它用于從DataLib類實例中的datas字段讀取數據
分析其中dataLib字段的用意。
注意并記住其中持續調用及使用隨機數的方法。
public class Reader implements Runnable{
private DataLib dataLib;
private static final Random random=new Random();
public Reader(DataLib dataLib){
this.dataLib=dataLib;
Thread thread=new Thread(this);
thread.start();
}
public void run(){
while(true){
Test.sleep(random.nextInt(2));
List<String> datas=dataLib.readData();
System.out.print(">>取得數組為:");
for(String data:datas){
System.out.print(data+",");
}
System.out.print("\n");
}
}
}
將代碼運行起來
右邊的代碼創建了兩個寫線程和三個讀線程,它們都是對dataLib實例進行操作的。
五個線程都有一個dataLib字段,都提供了一個帶參構造函數以給datas字段賦值,這就保證了五個線程操作的都是一個實例的同一字段,也就是同一片內存。
讀寫鎖就是對這五個線程進行控制的。
當有一個讀線程在操作時,其它的寫線程無法進行操作,讀線程可以正常操作,互不干擾。
當有一個寫線程在操作時,其它的讀線程無法進行操作。
public class Test{
public static void main(String[] args){
DataLib dataLib=new DataLib();
String[] mockDatas1={"甲","乙","丙","丁","戊","己","庚","辛","壬","癸"};
Writer writer1=new Writer(dataLib,mockDatas1);
String[] mockDatas2={"子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"};
Writer writer2=new Writer(dataLib,mockDatas2);
Reader reader1=new Reader(dataLib);
Reader reader2=new Reader(dataLib);
Reader reader3=new Reader(dataLib);
}
// 用于延時
public static void sleep(int sleepSecond){
try{
Thread.sleep(sleepSecond*1000);
}
catch(Exception ex){
ex.printStackTrace();
}
}
}
小結
當多個線程試圖對同一內容進行讀寫操作時適合使用讀寫鎖。
請理解并記住ReadWriteLock類讀寫鎖的寫法.
讀寫鎖相對于線程互斥的優勢在于高效,它不會對兩個讀線程進行盲目的互斥處理,當讀線程數量多于寫線程尤其如此,當全是寫線程時兩者等效。