天道不一定酬所有勤
但是,天道只酬勤

天津11选5遗漏数据:同步容器(如Vector)并不是所有操作都線程安全!~

開發十年,就只剩下這套架構體系了??!

天津11选5蛋托玩法 www.ijudhr.com.cn 轉移://www.ijudhr.com.cn/archives/3935

轉移://www.ijudhr.com.cn/archives/3935

轉移://www.ijudhr.com.cn/archives/3935

轉移://www.ijudhr.com.cn/archives/3935

轉移://www.ijudhr.com.cn/archives/3935

之前在公眾號中問了這個問題:對于線程安全的集合類(例如Vector)的任何操作是不是都能保證線程安全?

三天之內收到120+回復,其中表示不清楚的大概有10人左右,認為可以保證線程安全的有大概70人左右,認為不能保證線程安全的有50人左右,這其中能給出明確解釋的有5人。 分別是:

@趙鵬:

size方法和get方法,如果集合的長度變化了,可能拋出異常,

@aold619:

去網上查了資料:“有條件的線程安全 我們在 7 月份的文件“ 并發集合類”中討論了有條件的線程安全。有條件的線程安全類對于單獨的操作可以是線程安全的,但是某些操作序列可能需要外部同步。條件線程安全的最常見的例子是遍歷由 Hashtable 或者 Vector 或者返回的迭代器 — 由這些類返回的 fail-fast 迭代器假定在迭代器進行遍歷的時候底層集合不會有變化。為了保證其他線程不會在遍歷的時候改變集合,進行迭代的線程應該確保它是獨占性地訪問集合以實現遍歷的完整性。通常,獨占性的訪問是由對鎖的同步保證的 — 并且類的文檔應該說明是哪個鎖(通常是對象的內部監視器(intrinsic monitor))。 如果對一個有條件線程安全類進行記錄,那么您應該不僅要記錄它是有條件線程安全的,而且還要記錄必須防止哪些操作序列的并發訪問。用戶可以合理地假設其他操作序列不需要任何額外的同步?!?/p>

@閆曉琦:

答,不是,經?;岢魷質樵澆綾ù?/p>

@逆風飛揚:

vector單個的方法 synchronized并不代表vector組合的方法調用具有原子性。有組合的操作還是需要針對vector進行加鎖。

@慕容:

不是,線程安全集合只能保證單個操作安全,復合操作同樣不安全

那么這個問題的正解應該是什么的。

問:對于線程安全的集合類(例如Vector)的任何操作是不是都能保證線程安全?

答:同步容器中的所有自帶方法都是線程安全的,因為方法都使用synchronized關鍵字標注。但是,對這些集合類的復合操作無法保證其線程安全性。需要客戶端通過主動加鎖來保證

如果你看過JDK的源碼,那么你會發現,像Vector這樣的同步容器的所有共有方法全都是synchronized的。也就是說,我們可以在多線程場景中放心的使用單獨這些方法,因為這些方法本身的確是線程安全的。那么為什么又說復合操作無法保證線程安全呢?這里舉個栗子,我們定義如下刪除Vector中最后一個元素方法:

public Object deleteLast(Vector v){
    int lastIndex  = v.size()-1;
    v.remove(lastIndex);
}

上面這個方法是一個復合方法,包括size()remove(),乍一看上去好像并沒有什么問題,無論是size()方法還是remove()方法都是線程安全的,那么整個deleteLast方法應該也是線程安全的。但是時,如果多線程調用該方法的過程中有,remove方法有可能拋出ArrayIndexOutOfBoundsException。我們看一下remove方法具體實現,什么情況下會拋出這個異常呢。

public synchronized E remove(int index) {
    modCount++;
    if (index >= elementCount)
        throw new ArrayIndexOutOfBoundsException(index);
    E oldValue = elementData(index);

    int numMoved = elementCount - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--elementCount] = null; // Let gc do its work

    return oldValue;
}

從上面代碼中可以看出,當index >= elementCount時,會拋出ArrayIndexOutOfBoundsException,也就是說,當當前索引值不再有效的時候,將會拋出這個異常。因為removeLast方法,有可能被多個線程同時執行,當線程一通過index()獲得索引值為10,在嘗試通過remove()刪除該索引位置的元素之前,線程2把該索引位置的值刪除掉了,這時線程一在執行時便會拋出異常。

為了避免出現類似問題,可以嘗試加鎖:

public void deleteLast() {
    synchronized (v) {
        int index = v.size() - 1;
        v.remove(index);
    }
}

如上,我們在deleteLast中,對v進行加鎖,即可保證同一時刻,不會有其他線程刪除掉v中的元素。

至此,我們已經解釋清楚了我們的問題。

問:對于線程安全的集合類(例如Vector)的任何操作是不是都能保證線程安全?

答:同步容器中的所有自帶方法都是線程安全的,因為方法都使用synchronized關鍵字標注。但是,對這些集合類的復合操作無法保證其線程安全性。需要客戶端通過主動加鎖來保證。

由于我們自己已知Vector等同步容器是線程安全的,所以我們通常在多線程場景中會直接拿來使用,并不會考慮太多,從而可能導致問題。

所以,我們在使用同步容器的時候,如果只使用其中的自帶方法,那么可以放心使用,因為他們是線程安全的,但是如果我們想做復合操作,尤其是涉及到刪除容器中的元素時,一定要注意是否需要客戶端主動加鎖。

下面,我們考慮以下代碼,如果在多線程場景中使用會不會出現線程安全問題:

for (int i = 0; i < v.size(); i++) {
    System.out.println(v.get(i));
}

顯然,以上代碼在迭代的過程中,并不會出現線程安全問題。但是,如果在程序中還有以下代碼有可能被同時調用呢?

for (int i = 0; i < v.size(); i++) {
    v.remove(i);
}

由于,不同線程在同一時間操作同一個Vector,其中包括刪除操作,那么就同樣有可能發生線程安全問題。所以,在使用同步容器的時候,如果涉及到多個線程同時執行刪除操作,就要考慮下是否需要加鎖。

(全文完) 歡迎關注『Java之道』微信公眾號
贊(2)
如未加特殊說明,此網站文章均為原創,轉載必須注明出處。天津11选5蛋托玩法 » 同步容器(如Vector)并不是所有操作都線程安全!~
分享到: 更多 (0)

評論 3

  • 昵稱 (必填)
  • 郵箱 (必填)
  • 網址
  1. #1

    拜讀大俠博客,感悟人生道理!

    三五豪俠傳3年前 (2017-03-01)回復
  2. #2

    就是喜歡看你博客!

    增達網3年前 (2017-03-06)回復
  3. #3

    從百度進來的,拜讀一下貴站博文先
    丁酉年(雞)二月十五 2017-3-12

    增達網3年前 (2017-03-12)回復

HollisChuang's Blog

聯系我關于我