使用正確的鎖對象來保證線程安全

在java集合中,有些容器是線程安全的(Vector,ConcurrentLinkedQueue等),有些則不是(list等),對線程不安全的容器,可以利用類似

private static List<Task> taskQueue = Collections.synchronizedList(new LinkedList<Task>());的方法得到本身不是線程安全的容的線程安全的狀態。

但是要注意的一點,無論是哪一種情況,線程安全僅僅指的是如果直接使用它提供的函數,比如:queue.add(obj); 或者 queue.poll(obj);,這樣我們自己不需要做任何同步。
但如果是非原子操作,比如:
  if(!queue.isEmpty()) {  
     queue.poll(obj);  
  }  

這種非原子的操作,就不是線程安全的。
所以對於這種情況,我們還是需要自己同步:
   synchronized(queue) {  
        if(!queue.isEmpty()) {  
          queue.poll(obj);  
        }  
    }  

在這種同步的過程中,經常會出現使用了不同的鎖導致的錯誤情況:

下面是一個錯誤例子:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ListHelper<E> {
	public List<E> list = Collections.synchronizedList(new ArrayList<E>());
	
	public synchronized boolean putIfAbsent(E x){
		boolean absent = !list.contains(x);
		if(absent)
			list.add(x);
		return absent;
	}
}
上面的例子中,雖然putIfAbsent方法使用了synchronized,但是synchronized使用的鎖是ListHelper對象的內置鎖,跟Collections.synchronizedList使用的鎖是不一樣的鎖,所以會出現當一個線程進入putIfAbsent方法後,還是會有線程對list列表進行修改,這樣在putIfAbsent方法中的非原子操作方法中,會出現線程不同步的情況。

要改正這種情況,就要使用同一鎖,由於public List<E> list = Collections.synchronizedList(new ArrayList<E>());使用的鎖是list對象(這等下會在下面寫一個類來證明),所以在putIfAbsent方法中,我們要使用list對象來當鎖:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ListHelper<E> {
	public List<E> list = Collections.synchronizedList(new ArrayList<E>());
	
	public  boolean putIfAbsent(E x){
		synchronized(list){
			boolean absent = !list.contains(x);
			if(absent)
				list.add(x);
			return absent;
		}
	}
}
使用同一個鎖後,只要是進入了同步代碼塊,另一個線程想要再修改list就會等待釋放鎖後才執行,這就能保證線程同步。

下面來證明下public List<E> list = Collections.synchronizedList(new ArrayList<E>());使用的鎖是list對象。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


public class Vector1 {
	
	public static void main(String[] args) {
		ListHelper lh = new ListHelper();
		new Thread(new Thread1(lh)).start();
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		lh.list.add(2);
		System.out.println("+++++++++++++++++");
		
	}
}

class Thread1 implements Runnable{
	ListHelper lh;
	public Thread1(ListHelper lh){
		this.lh = lh;
	}

	@Override
	public void run() {
		lh.putIfAbsent(10);
	}
	
}

class ListHelper{
	public List<Integer> list = Collections.synchronizedList(new ArrayList<Integer>());
	
	public boolean putIfAbsent(Integer x){
		synchronized (list) {
			System.out.println("===============");
			try {
				Thread.sleep(10000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("-------------------------");
			return true;
		}
	}
	
	
	
}

上面的執行結果是:

===============
-------------------------
+++++++++++++++++

由於子線程先執行了putIfAbsent方法,進入了同步代碼塊,這時list鎖被子線程拿走了,所以即使主線程休眠時間先結束要執行lh.list.add(2);時,由於子線程還沒執行完,不能拿到鎖,所以這時主線程等待,直道子線程執行完後,才返回鎖給主線程,才接着輸入+++

如果把要拿鎖才能執行的一行代碼註釋掉://lh.list.add(2);

然後再執行,輸入結果如下:

===============
+++++++++++++++++
-------------------------


發佈了73 篇原創文章 · 獲贊 3 · 訪問量 8萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章