程式碼淺析 Android Lock 、ReentrantLock執行緒鎖及其作用
阿新 • • 發佈:2019-02-01
先來了解什麼是“互斥鎖”?
百度一下,解釋如下:在程式設計中,引入了物件互斥鎖的概念,來保證共享資料操作的完整性。每個物件都對應於一個可稱為" 互斥鎖" 的標記,這個標記用來保證在任一時刻,只能有一個執行緒訪問該物件。
是的,面對高併發的讀、寫訪問,可能會出現資料丟失的問題,而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帶來的好處,它在保證共享資料併發操作的完整性和一致性,最重要的是提高了讀寫的效率~!