1. 程式人生 > >Java併發-對容器類的原子操作

Java併發-對容器類的原子操作

我們知道很多Java容器類都是非執行緒安全的,比如常用的ArrayList就是非執行緒安全的,表現在併發場景下,經常出現莫名其妙的錯誤,看個例子:

public class ConcurrentAccessListTest {

    public static List<Integer> LIST = new ArrayList<>();

    static {
        LIST.add(1);
        LIST.add(2);
        LIST.add(3);
        LIST.add(4);
        LIST.add(5);
        LIST.add(6);
        LIST.add(7);
        LIST.add(8);
        LIST.add(9);
        LIST.add(10);
    }
/**
 * 控制訪問LIST的入口
 * @return
 */
public synchronized static List<Integer> getLIST() {
    return LIST;
}
    public static int getLast() {
        return LIST.get(LIST.size() -1);
    }

    public static void deleteLast() {
        LIST.remove(LIST.size() -1);
    }

    public static void main(String[] args){
        new Thread(){
            @Override
            public void run(){
                int i = 10;
                while(i-- >0){
                    //獲取尾元素
                    System.out.println(getLast(getLIST()));
                }
            }
        }.start();
        new Thread(){
            @Override
            public void run() {
                int i = 10;
                while(i-- >0){
                    //刪除尾元素
                    deleteLast(getLIST());
                }
            }
        }.start();
    }
}

上邊的程式碼,我運行了20次,平均每執行4次就會丟擲ArrayIndexOutOfBoundsException異常,異常的原因是併發地修改List,結果的正確性完全靠運氣,因為併發執行的時序是隨機的。

報錯截圖如下:

解決的辦法是加鎖。

public static synchronized int getLast() {
    return LIST.get(LIST.size() -1);
}

public static synchronized void deleteLast() {
    LIST.remove(LIST.size() -1);
}

依然報錯,截圖如下:

原因是使用了2把鎖,企圖用ConcurrentAccessListTest物件鎖去控制對LIST的併發訪問,須知,必須保證鎖是唯一的才可以保證複合操作的併發的原子性!

正確的方式是使用CopyOnWriteArrayList。