在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);
然後再執行,輸入結果如下:
===============
+++++++++++++++++
-------------------------