1. 程式人生 > >Java執行緒總結(八):併發包------讀寫鎖ReadWriteLock的簡單例子詳細理解

Java執行緒總結(八):併發包------讀寫鎖ReadWriteLock的簡單例子詳細理解

初次接觸ReadWriteLock類時也在網上查了很多資料,很容易瞭解到ReadWriteLock是讀寫鎖,並且讀寫鎖的機制有以下三個特點:

  讀鎖---讀鎖    (不互斥)

  讀鎖---寫鎖     (互斥)

  寫鎖---寫鎖     (互斥)

什麼意思呢?

網上很多資料,直接用這三個特點實現一個快取的例子進行了講解,但是對小白來說還有那麼一絲絲的迷惑(老鳥忽略),下面就逐一演示:

1. 讀鎖---讀鎖

main方法裡兩個執行緒都在讀資料,在read()方法裡已經上了讀鎖且沒有解鎖(為了測試)

public class ReadWriteLockTest {
	public static void main(String[] args) {
		final ReadAndWrite raw = new ReadAndWrite();
		raw.map.put("data", 1);
		//執行緒1,讀資料
		new Thread(
				new Runnable() {
					@Override
					public void run() {
						while(true)
							raw.read();						
					}
		}).start();
		//執行緒2,讀資料
		new Thread(
				new Runnable() {
					@Override
					public void run() {
						while(true)
							raw.read();
					}
		}).start();
		
	}
}


class ReadAndWrite{
	ReadWriteLock rwlock = new ReentrantReadWriteLock();//讀寫鎖
	Map map = new HashMap();//共享的資料
	public void read(){
		rwlock.readLock().lock();//上讀鎖,且沒有解鎖
		System.out.println(Thread.currentThread().getName()+"讀開始...");
		System.out.println(Thread.currentThread().getName()+"讀資料為:"+map.get("data"));
		System.out.println(Thread.currentThread().getName()+"讀結束...");
		try {
			Thread.sleep((long) (Math.random()*1000));
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

(可以看到兩個執行緒都能不斷讀資料,記住這個現象,接著看下面的例子進行對比)

執行結果:

Thread-0讀開始...
Thread-0讀資料為:1
Thread-0讀結束...
Thread-1讀開始...
Thread-1讀資料為:1
Thread-1讀結束...
Thread-1讀開始...
Thread-1讀資料為:1
Thread-1讀結束...
Thread-0讀開始...
Thread-0讀資料為:1
Thread-0讀結束...

2. 讀鎖---寫鎖 

main方法裡執行緒1在讀資料,執行緒2在寫資料,將read()方法裡的“解讀鎖”註釋掉和不註釋分別執行對比結果

public class ReadWriteLockTest2 {
	public static void main(String[] args) {
		final ReadAndWrite raw = new ReadAndWrite();
		raw.map.put("data", 1);
		//執行緒1,讀資料
		new Thread(
				new Runnable() {
					@Override
					public void run() {
						while(true)
							raw.read();						
					}
		}).start();
		//執行緒2,寫資料
		new Thread(
				new Runnable() {
					@Override
					public void run() {
						while(true)
							raw.write();
					}
		}).start();
		
	}
}

class ReadAndWrite{
	ReadWriteLock rwlock = new ReentrantReadWriteLock();//讀寫鎖
	Map map = new HashMap();//共享的資料
	public void read(){
		rwlock.readLock().lock();//上讀鎖
		System.out.println(Thread.currentThread().getName()+"讀開始...");
		System.out.println(Thread.currentThread().getName()+"讀資料為:"+map.get("data"));
		System.out.println(Thread.currentThread().getName()+"讀結束...");
		<span style="color:#ff0000;">//rwlock.readLock().unlock();//解讀鎖</span>
		try {
			Thread.sleep((long) (Math.random()*1000));
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	public void write(){
		rwlock.writeLock().lock();//上寫鎖
		System.out.println(Thread.currentThread().getName()+"寫開始...");
		double data = Math.random();
		map.put("data", data);
		System.out.println(Thread.currentThread().getName()+"寫資料為:"+data);
		System.out.println(Thread.currentThread().getName()+"寫結束...");
		rwlock.writeLock().unlock();//解寫鎖
		try {
			Thread.sleep((long) (Math.random()*1000));
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

read()方法裡“解讀鎖”註釋掉,(可以看到只有執行緒1在讀資料,執行緒2是阻塞狀態無法寫資料,證明了讀鎖和寫鎖的互斥

執行結果:

Thread-0讀開始...
Thread-0讀資料為:1
Thread-0讀結束...
Thread-0讀開始...
Thread-0讀資料為:1
Thread-0讀結束...

read()方法裡“解讀鎖”不註釋,(可以看到執行緒1和執行緒2交替的讀和寫資料,也就是讀鎖解了,其他執行緒就可以寫資料了,寫鎖解了,其他執行緒就可以讀資料了

執行結果:

Thread-0讀開始...
Thread-0讀資料為:1
Thread-0讀結束...
Thread-1寫開始...
Thread-1寫資料為:0.4387383401358649
Thread-1寫結束...

3. 寫鎖---寫鎖

相信看了上面兩個例子,基本就明白了寫鎖和寫鎖是怎麼互斥的,同樣執行緒1和執行緒2都進行寫資料,write()方法上寫鎖並且不解鎖(測試才這樣寫的)

public class ReadWriteLockTest3 {
	public static void main(String[] args) {
		final ReadAndWrite raw = new ReadAndWrite();
		raw.map.put("data", 1);
		//執行緒1,寫資料
		new Thread(
				new Runnable() {
					@Override
					public void run() {
						while(true)
							raw.write();						
					}
		}).start();
		//執行緒2,寫資料
		new Thread(
				new Runnable() {
					@Override
					public void run() {
						while(true)
							raw.write();
					}
		}).start();
		
	}
}

class ReadAndWrite{
	ReadWriteLock rwlock = new ReentrantReadWriteLock();//讀寫鎖
	Map map = new HashMap();//共享的資料
	public void write(){
		rwlock.writeLock().lock();//上寫鎖
		System.out.println(Thread.currentThread().getName()+"寫開始...");
		double data = Math.random();
		map.put("data", data);
		System.out.println(Thread.currentThread().getName()+"寫資料為:"+data);
		System.out.println(Thread.currentThread().getName()+"寫結束...");
		try {
			Thread.sleep((long) (Math.random()*1000));
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}
(write()方法裡上了寫鎖並且沒有解鎖,所以只有一個執行緒在寫資料,其他執行緒無法進行寫資料,你也可以把解鎖給加上,執行一下對比看看)
執行結果:

Thread-0寫開始...
Thread-0寫資料為:0.2439106501627466
Thread-0寫結束...
Thread-0寫開始...
Thread-0寫資料為:0.44688857423885386
Thread-0寫結束...
Thread-0寫開始...

相信通過以上的程式碼和執行結果進行對比和觀察,應該已經理解了ReadWriteLock三個特點,下面貼上實現快取類的程式碼(jdk中ReentrantReadWriteLock類的一個例子):

public class Cache {
	private Map<String, Object> cache = new HashMap<String, Object>();
	private ReadWriteLock rwLock = new ReentrantReadWriteLock();

	public Object getData(String key) {
		// 首先上讀鎖
		rwLock.readLock().lock();
		// 首先從快取中獲取
		Object value = null;
		try {
			Thread.sleep(1000);
			value = cache.get(key);
			if (value == null) {
				// 如果快取中沒有資料,那麼就從資料庫中獲取
				// 但此時需要上寫鎖,只需要讓一個程序進行寫資料
				// 首先去除讀鎖,然後加上寫鎖
				rwLock.readLock().unlock();
				rwLock.writeLock().lock();
				try {
					// 注意防止多執行緒執行到上一步,某個執行緒寫完資料後
					// 別的執行緒就需要看是否有資料再決定是否進行寫操作
					// 在寫之前再讀一次,防止最開始的執行緒都進行寫操作</span>
					value = cache.get(key);
					// 第一個執行緒寫完後,防止後面的執行緒再次寫資料
					if (value == null) {
						System.out.println("有執行緒寫資料........");
						value = "資料庫中獲取";
						// 將資料放入快取
						cache.put(key, value);
						System.out.println("資料寫完了.......");
					}
				} finally {
					rwLock.readLock().lock();// 恢復讀鎖,鎖的重入
					rwLock.writeLock().unlock();
				}
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		} finally {
			rwLock.readLock().unlock();// 解讀鎖
		}
		return value;
	}
}