Java併發程式設計實踐筆記(一)
public class ListHelper<Integer> { public List<Integer> list = Collections .synchronizedList(new ArrayList<Integer>()); // private List<E> list = Collections.synchronizedList(new ArrayList<E>()); public synchronized boolean put(Integer i) { list.add(i); System.out.println(Thread.currentThread().getName() + " --- " + i); return true; } }
程式碼說明1)變數list用Collections.synchronizedList的作用是把本身不是執行緒安全的容器ArrayList變成執行緒安全的
2)ListHepler的方法都用了synchronized來進行加鎖,用來同步。
3)注意list變數的訪問許可權是public!
現在提供兩個執行緒類來提供模擬這個同步:
執行緒A和執行緒B的程式碼如下(兩個程式碼除了類名字完全一樣,所以在此僅貼出來A的)
public class A extends Thread { private ListHelper<Integer> lh; public A(ListHelper<Integer> lh) { super("A"); this.lh = lh; } public void run() { synchronized (lh) { for (int i = 0; i < 9; i++) { lh.put(i); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } }
客戶端的程式碼:
public class Client {
public static void main(String args[]) throws InterruptedException{
ListHelper<Integer> lh = new ListHelper<Integer>();
A a = new A(lh);
B b = new B(lh);
a.start();
b.start();
}
}
執行結果如下:A --- 0
A --- 1
A --- 2
A --- 3
A --- 4
A --- 5
A --- 6
A --- 7
A --- 8
B --- 0
B --- 1
B --- 2
B --- 3
B --- 4
B --- 5
B --- 6
B --- 7
B --- 8
從執行結果來看,一切都是那麼順利,當執行緒A執行的時候,B阻塞;然後A執行完畢釋放鎖,B獲取鎖並執行。看起來很安全的樣子。
但是下面在提供一個thread C:
public class C extends Thread {
private ListHelper<Integer> lh;
public C(ListHelper<Integer> lh){
super("C");
this.lh = lh;
}
public void run() {
synchronized (lh.list) {//添加了list鎖
for (int i = 0; i < 9; i++) {
System.out.println(Thread.currentThread().getName() + "------" + i);
lh.list.add(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
修改客戶端程式碼如下:
public static void main(String args[]) throws InterruptedException{
ListHelper<Integer> lh = new ListHelper<Integer>();
A a = new A(lh);
B b = new B(lh);
C c = new C(lh);
a.start();
b.start();
c.start();
}
執行結果如下:
A --- 0
C------0
C------1
C------2
C------3
C------4
C------5
C------6
C------7
C------8
A --- 1
A --- 2
A --- 3
A --- 4
A --- 5
A --- 6
A --- 7
A --- 8
B --- 0
B --- 1
B --- 2
B --- 3
B --- 4
B --- 5
B --- 6
B --- 7
B --- 8
發現執行緒A在執行的時候,由於A獲取了ListHelper的鎖,導致B執行緒的阻塞,當執行緒A在執行完的時候釋放鎖,然後B獲取鎖得到執行。但是有如下問題出現了
1)變數list不是被Collectoion.synchronizedList加過鎖了麼?
2)在A獲取鎖並執行的時候C怎麼可以執行呢?
3)並且A只執行了一次put方法之後等著C執行完畢後才得到執行呢?
解答:
A獲取的是ListHelper物件鎖,而Collection.synchronizedList為list新增的鎖是另外一個鎖,也就是說兩個鎖不是一回事兒。所以問題3就可以迎刃而解了:
1)A執行一次put方法,釋放了list鎖。此時A仍然擁有ListHelper鎖,B在等待獲取ListHelper鎖,所以B仍然阻塞
2)C獲取到了list鎖執行完for迴圈並釋放list鎖,A得到list鎖並執行完for迴圈,釋放ListHelper鎖。
3)B得到ListHelper鎖,並執行完畢,程式退出
所以A執行緒獲取ListHelper物件鎖執行的並執行的時候是沒法阻塞C執行緒的執行的,除非A執行緒也獲取了list上的鎖!!!
所以可以把A和B的run方法程式碼改成:
public void run() {
synchronized (lh) {
synchronized (lh.list) {//添加了list鎖
for (int i = 0; i < 9; i++) {
lh.put(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
}
再次執行獲得結果:
A --- 0
A --- 1
A --- 2
A --- 3
A --- 4
A --- 5
A --- 6
A --- 7
A --- 8
C------0
C------1
C------2
C------3
C------4
C------5
C------6
C------7
C------8
B --- 0
B --- 1
B --- 2
B --- 3
B --- 4
B --- 5
B --- 6
B --- 7
B --- 8
當然要把ListHelper設定成執行緒安全的類,最簡單的是不釋出list變數,可以把list的改成private同時不提供getList()方法,不過這樣的話Collection.synchronizedList就失去了它的作用。怎麼在public訪問許可權不變的情況下,只利用Collection.synchronizedList提供的鎖來控制執行緒同步的呢?上面的程式碼A和B明顯用到了兩個鎖,一個是ListHelper鎖,另一個是Collection.synchronizedList提供的鎖,其實完全可以利用後者而不用ListHelper鎖來完成上述的更能。
修改ListHelper程式碼如下:
public class ListHelper<Integer> {
public List<Integer> list = Collections.synchronizedList(new ArrayList<Integer>());
// private List<E> list = Collections.synchronizedList(new ArrayList<E>());
public boolean put(Integer i) {
synchronized(list){//在list上加鎖
list.add(i);
System.out.println(Thread.currentThread().getName() + " --- " + i);
return true;
}
}
}
這樣的話上面的A和B的run方法就可以去掉synchronized (lh) 而只用synchronized (lh.list) 使得程式安全併發的運行了
public void run() {
//synchronized (lh) {
synchronized (lh.list) {
for (int i = 0; i < 9; i++) {
lh.putIfAbsent(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
//}
}