1. 程式人生 > >程式碼淺析 Android Lock 、ReentrantLock執行緒鎖及其作用

程式碼淺析 Android Lock 、ReentrantLock執行緒鎖及其作用

先來了解什麼是“互斥鎖”?

百度一下,解釋如下:在程式設計中,引入了物件互斥鎖的概念,來保證共享資料操作的完整性。每個物件都對應於一個可稱為" 互斥鎖" 的標記,這個標記用來保證在任一時刻,只能有一個執行緒訪問該物件。

是的,面對高併發的讀、寫訪問,可能會出現資料丟失的問題,而Andriod系統基於Linux核心,使得併發讀、寫資料可以沒有限制的進行。因此,出於對資料,尤其是共享資料的完整性和一致性,我們需要用到鎖機制來確保資料的可靠性。

在Android中,有三類鎖可以解決上述問題:

1:synchronized

這是java中比較常見的一種同步鎖關鍵字,由於常見,這裡就不一一介紹了,詳見:http://blog.csdn.net/luoweifu/article/details/46613015

2:Lock
先看看繼承關係:


使用之前我們先看看,模擬併發的情況:

package SychronizedTest;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SychronizedTest {

	/**
	 * @author zy_style
	 * @param args
	 */
	public static void main(String[] args) {
		final Outputter outputter = new Outputter();
		// 開啟一條執行緒輸出名字的每個字元
		new Thread() {
			@Override
			public void run() {
				try {
					outputter.output("abcde");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}.start();
		// 開啟另一條執行緒
		new Thread() {
			@Override
			public void run() {
				try {
					outputter.output("fghij");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}.start();
	}
}

class Outputter {
	public void output(String name) throws InterruptedException {
		for (int i = 0; i < name.length(); i++) {
			System.out.println(name.charAt(i));
			Thread.sleep(1000);
		}
	}
}

得到的輸出結果:

a
f
g
b
h
c
d
i
j
e
可以看到,確實如我們想象的那樣,輸出的內容混在了一起,如果是對檔案進行讀寫的話,就不能保證檔案的完整性了。

現在我們加上Lock:

package SychronizedTest;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SychronizedTest {

	/**
	 * @author zy_style
	 * @param args
	 */
	public static void main(String[] args) {
		final Outputter outputter = new Outputter();
		// 開啟一條執行緒輸出名字的每個字元
		new Thread() {
			@Override
			public void run() {
				try {
					outputter.output("abcde");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}.start();
		// 開啟另一條執行緒
		new Thread() {
			@Override
			public void run() {
				try {
					outputter.output("fghij");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}.start();
	}
}

class Outputter {
	private Lock lock = new ReentrantLock(); // 定義鎖物件

	public void output(String text) throws InterruptedException {
		lock.lock(); // 得到鎖
		try {
			for (int i = 0; i < text.length(); i++) {
				System.out.println(text.charAt(i));
				Thread.sleep(1000);
			}
		} finally {
			lock.unlock(); // 釋放鎖
		}
	}
}

現在,輸出結果如下:
a
b
c
d
e
f
g
h
i
j
可以發現,輸出的結果變得有序多了,一個執行緒獲得鎖執行完操作後,釋放鎖,下一個執行緒才能進行操作,這樣才能保證併發操作有序的進行,而不是一盤散撒。

3:ReadWriteLock
可以發現,上面的情況並不能解決併發讀寫的同步問題,比如說讀與寫互斥、寫與寫互斥的問題。先看看不考慮互斥問題的程式碼:
package SychronizedTest;


/**
 * 讀寫鎖測試
 * 
 * @author zy_style
 */
public class ReadWriteLockTest {

	/**
	 * @author zhouyang 2016-12-1
	 * @time 下午3:01:27
	 * @param args
	 */
	public static void main(String[] args) {
		final Data data = new Data();
		// 開啟3個子執行緒,分別寫入資料
		for (int i = 0; i < 3; i++) {
			new Thread() {
				public void run() {
					for (int j = 0; j < 5; j++) {
						try {
							data.set(j); // 寫入資料
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				};
			}.start();
		}

		// 分別讀取資料
		for (int i = 0; i < 3; i++) {
			new Thread() {
				public void run() {
					for (int j = 0; j < 5; j++) {
						try {
							data.get(); // 讀取資料
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				};
			}.start();
		}
	}

}

/**
 * 需要操作的資料
 * 
 * @author zy_style
 */
class Data {
	private int data;

	public void set(int data) throws Exception {
		System.out.println(Thread.currentThread().getName() + "準備寫入資料...");
		Thread.sleep(50); // 模擬耗時操作
		this.data = data;
		System.out.println(Thread.currentThread().getName() + "寫入資料成功!");
	}

	public void get() throws Exception {
		System.out.println(Thread.currentThread().getName() + "準備讀取資料...");
		Thread.sleep(50); // 模擬耗時操作
		System.out.println(Thread.currentThread().getName() + "讀取資料:"
					+ this.data);
	}
}
部分執行結果如下:
Thread-1寫入資料成功!
Thread-1準備寫入資料...
Thread-2寫入資料成功!
Thread-2準備寫入資料...
Thread-0準備寫入資料...
Thread-4讀取資料:0
Thread-4準備讀取資料...
Thread-3讀取資料:0
Thread-5讀取資料:0
Thread-3準備讀取資料...
Thread-5準備讀取資料...
Thread-1寫入資料成功!
沒有任何規律可言,而且容易造成資料混亂和丟失。

如果現在有這麼一個需求:讀與寫互斥、寫與寫互斥、讀與讀互斥,說白了就是讓所有的讀寫操作都互斥,互不干擾。其實很簡單,在set()和get()方法前加上sychronized修飾符即可:

public synchronized void set(int data) throws Exception {
		System.out.println(Thread.currentThread().getName() + "準備寫入資料...");
		Thread.sleep(50); // 模擬耗時操作
		this.data = data;
		System.out.println(Thread.currentThread().getName() + "寫入資料成功!");
	}

public synchronized void get() throws Exception {
		System.out.println(Thread.currentThread().getName() + "準備讀取資料...");
		Thread.sleep(50); // 模擬耗時操作
		System.out.println(Thread.currentThread().getName() + "讀取資料:"
					+ this.data);
	}

部分執行結果如下:
Thread-0準備寫入資料...
Thread-0寫入資料成功!
Thread-0準備寫入資料...
Thread-0寫入資料成功!
Thread-5準備讀取資料...
Thread-5讀取資料:4
Thread-4準備讀取資料...
Thread-4讀取資料:4
Thread-4準備讀取資料...
Thread-4讀取資料:4
Thread-3準備讀取資料...
Thread-3讀取資料:4

既然結果確實是讀、寫互斥了,但是考慮到實際情況,我們為了提高併發的效率,其實讀與讀可以不互斥,只用保證讀與寫、讀與讀之間互斥,這樣才能最高效的執行併發操作並保證資料的完整性。這裡就用到ReadWriteLock了:

package SychronizedTest;

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * 讀寫鎖測試
 * 
 * @author zy_style
 */
public class ReadWriteLockTest {

	/**
	 * @author zhouyang 2016-12-1
	 * @time 下午3:01:27
	 * @param args
	 */
	public static void main(String[] args) {
		final Data data = new Data();
		// 開啟3個子執行緒,分別寫入資料
		for (int i = 0; i < 3; i++) {
			new Thread() {
				public void run() {
					for (int j = 0; j < 5; j++) {
						try {
							data.set(j); // 寫入資料
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				};
			}.start();
		}

		// 分別讀取資料
		for (int i = 0; i < 3; i++) {
			new Thread() {
				public void run() {
					for (int j = 0; j < 5; j++) {
						try {
							data.get(); // 讀取資料
						} catch (Exception e) {
							e.printStackTrace();
						}
					}
				};
			}.start();
		}
	}

}

/**
 * 需要操作的資料
 * 
 * @author zy_style
 */
class Data {
	private int data;
	private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();

	public void set(int data) throws Exception {
		readWriteLock.writeLock().lock(); // 獲取寫鎖
		try {
			System.out.println(Thread.currentThread().getName() + "準備寫入資料...");
			Thread.sleep(50); // 模擬耗時操作
			this.data = data;
			System.out.println(Thread.currentThread().getName() + "寫入資料成功!");
		} finally {
			readWriteLock.writeLock().unlock(); // 釋放寫鎖
		}

	}

	public void get() throws Exception {
		readWriteLock.readLock().lock(); // 獲取讀鎖
		try {
			System.out.println(Thread.currentThread().getName() + "準備讀取資料...");
			Thread.sleep(50); // 模擬耗時操作
			System.out.println(Thread.currentThread().getName() + "讀取資料:"
					+ this.data);
		} finally {
			readWriteLock.readLock().unlock(); // 釋放讀鎖
		}

	}
}

部分執行結果如下:
Thread-1準備寫入資料...
Thread-1寫入資料成功!
Thread-2準備寫入資料...
Thread-2寫入資料成功!
Thread-5準備讀取資料...
Thread-3準備讀取資料...
Thread-4準備讀取資料...
Thread-5讀取資料:0
Thread-3讀取資料:0
Thread-4讀取資料:0

可以看到,讀與寫、寫與寫確實互斥了,但是讀與讀沒有互斥,這就是ReadWriteLock帶來的好處,它在保證共享資料併發操作的完整性和一致性,最重要的是提高了讀寫的效率~!