1. 程式人生 > >Java併發程式設計實踐筆記(一)

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();
					}
				}
			}
			
		}

	//}
}