LinkedList線程安全問題


Java中LinkedList是線程不安全的,那么如果在多線程程序中有多個線程訪問LinkedList的話會出現什么問題呢?

拋出ConcurrentModificationException

JDK代碼里,ListItr的add(), next(), previous(), remove(), set()方法都會跑出ConcurrentModificationException。

final void checkForComodification() { 
        if (modCount != expectedModCount) 
        throw new ConcurrentModificationException(); 
    }

代碼中,modCount記錄了LinkedList結構被修改的次數。Iterator初始化時,expectedModCount=modCount。任何通過Iterator修改LinkedList結構的行為都會同時更新expectedModCount和modCount,使這兩個值相等。通過LinkedList對象修改其結構的方法只更新modCount。所以假設有兩個線程A和B。A通過Iterator遍歷並修改LinkedList,而B,與此同時,通過對象修改其結構,那么Iterator的相關方法就會拋出異常。這是相對容易發現的由線程競爭造成的錯誤。

通過LinkedList對象修改其結構

如果兩個線程都是通過LinkedList對象修改其結構,會發生什么呢?我們先看一下JDK中LinkedList的數據結構。

 

這是一個雙向循環鏈表。header的作用就是能快速定位鏈表的頭和尾。圖中,“n”表示next,“p”表示previous。header的n指向first element;p指向last element。當一個線程A調用LinkedList的addFirst方法時(假設添加節點“4”),它首先更新“4”的n和p,n->3, p->header。此為第一步。第二步,更新節點“3”和herder的n和p,3n不變, 3p->4, headern->4, headerp不變。假設兩個線程A,B同時調用addFirst(4), addFirst(5),會發生什么呢?很可能4,5的n指向3,p都指向header。(這里不是很確定,沒有寫代碼測試,貌似無法從LinkedList獲得entry)。也可能addFirst后,緊接着發現getFirst已經不是剛剛加入的元素。

package linkedlist;

import java.util.Iterator; 
import java.util.LinkedList; 
import java.util.concurrent.Executors;

public class Test {

    private final LinkedList<Integer> list = new LinkedList<Integer>();

    public Test() { 
        Thread add1 = new Thread(new Runnable() {

            @Override 
            public void run() { 
                int number = 0; 
                while (true) { 
                    if (number == 10) 
                        number = 0; 
                    // System.out.println("Writing " + number); 
                    list.addFirst(number); 
                    int first = list.getFirst(); 
                    if (first != number) { 
                        System.err.println("ERROR!!! " + number + " " + first); 
                        System.exit(-1); 
                    } 
                    number++; 
                    System.out.flush(); 
                    try { 
                        Thread.sleep(2); 
                    } catch (InterruptedException e) { 
                        // TODO Auto-generated catch block 
                        e.printStackTrace(); 
                    } 
                } 
            } 
        });

        Thread add2 = new Thread(new Runnable() {

            @Override 
            public void run() { 
                int number = 100; 
                while (true) { 
                    if (number > 119) { 
                        number = 100; 
                    } 
                    // System.out.println("Writing " + letter); 
                    // System.out.flush(); 
                    list.addFirst(number); 
                    int first = list.getFirst(); 
                    if (first != number) { 
                        System.err.println("ERROR!!! " + number + " " + first); 
                        System.exit(-1); 
                    } 
                    number++; 
                    try { 
                        Thread.sleep(2); 
                    } catch (InterruptedException e) { 
                        // TODO Auto-generated catch block 
                        e.printStackTrace(); 
                    } 
                } 
            } 
        });

        Executors.newCachedThreadPool().execute(add1); 
        Executors.newCachedThreadPool().execute(add2); 
        // Executors.newCachedThreadPool().execute(read2); 
    }

    /** 
     * @param args 
     */ 
    public static void main(String[] args) { 
        Test test = new Test(); 
    } 
}

程序輸出:

ERROR!!! 0 100

總結一下,線程安全問題是由多個線程同時寫或同時讀寫同一個資源造成的。這里只是通過分析LinkedList的相關源碼淺顯的分析,下一步就要考慮如何解決或避免線程競爭了。

 

建議辦法是:
將LinkedList全部換成ConcurrentLinkedQueue試試,LinkedList是線程不安全的。
有興趣可以參考http://byline.ow2.org/ccm-core-6.1.0/api/com/arsdigita/developersupport/Comodifications.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM