【多線程數據不安全的代碼示例】

一、實例變量+復合操作(read-set-write)

package com.tinygao.thread.unsafe;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import lombok.extern.slf4j.Slf4j;

/**
 * Created by gsd on 2017/2/5.
 */
@Slf4j
public class Counter {
    public static int count = 0;

    public static void inc() {
        count++;
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService es = Executors.newFixedThreadPool(1000);
        for(int i = 0; i < 10000000; i++) {
            es.submit(Counter::inc);
        }
        es.shutdown();
        es.awaitTermination(1, TimeUnit.DAYS);
        log.info("count:{}", count);
    }
}

count值不等于10000000

打印出來的日志是這種執行順序
沒有打印出來的是這種執行順序

二、成員變量+復合操作(if-then)

package com.tinygao.thread.unsafe;

import com.google.common.base.Preconditions;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.*;

import static java.lang.Thread.sleep;
import static sun.swing.SwingUtilities2.submit;

/**
 * Created by gsd on 2017/2/4.
 */
@Slf4j
public class SingletonLazyInit {
    private static SingletonLazyInit instance = null;

    public static SingletonLazyInit getInstance() throws InterruptedException {
        if(instance == null) {
            //TimeUnit.NANOSECONDS.sleep(1);
            instance = new SingletonLazyInit();
        }
        return instance;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService es = Executors.newFixedThreadPool(2);
        for(int i = 0; i < 200; i++) {
            Future<SingletonLazyInit> obj1 = es.submit(()->{
                return SingletonLazyInit.getInstance();
            });
            Future<SingletonLazyInit> obj2 = es.submit(()->{
                return SingletonLazyInit.getInstance();
            });
            try {
                Preconditions.checkState(obj1.get() == obj2.get(), "error ----> obj1:%s, ojb2:%s",
                        obj1.get().hashCode(),
                        obj2.get().hashCode());
            } catch (IllegalStateException ex) {
                log.error(ex.getMessage());
            }
            /*log.info("obj1:{}, ojb2:{}",
                    obj1.get().hashCode(),
                    obj2.get().hashCode());*/

            instance = null;
        }
        es.shutdown();
    }
}

總有概率性的輸出error日志。
單例模式創建了兩個對象。原因與上一個例子一樣。

3、實例變量

package com.tinygao.thread.unsafe;

import com.google.common.base.Preconditions;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Created by gsd on 2017/2/4.
 */
@Slf4j
public class SetValue {
    @Getter
    private String value = "";

    public void setValue(String value) {
        this.value = value;
    }

    public static void main(String[] args) {
        SetValue sv = new SetValue();
        ExecutorService es = Executors.newFixedThreadPool(1000);
        for(int i = 0; i < 1000000; i++) {
            es.submit(()->{
                String expect = Thread.currentThread().getName();
                sv.setValue(Thread.currentThread().getName());
                String result = sv.getValue();
                try {
                    Preconditions.checkState(result.equals(expect),
                                            "expect:%s, result:%s",
                                            expect,
                                            result);
                } catch (IllegalStateException e) {
                    log.error(e.getMessage());
                }
            });
        }
        es.shutdown();
    }
}

4、線程安全類+復合操作(if-then)也會不安全哦

package com.tinygao.thread.unsafe;

import com.google.common.base.Preconditions;
import lombok.extern.slf4j.Slf4j;

import java.util.Map;
import java.util.concurrent.*;

/**
 * Created by gsd on 2017/2/4.
 */
@Slf4j
public class ConcurrentHashMapTest {
    private static Map<String, String> map = new ConcurrentHashMap<>();
    private static int a = 0;

    public static void setValue(String key, String value) {
        if(!map.containsKey(key)) {
            a++;
            map.put(key, value);
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService es = Executors.newFixedThreadPool(2);

        for(int i = 0; i < 1000; i++) {
            Future<Void> thread1 = es.submit(()->{
                ConcurrentHashMapTest.setValue("key","value1");
                return null;
            });
            Future<Void>  thread2 = es.submit(()->{
                ConcurrentHashMapTest.setValue("key","value2");
                return null;
            });
            try {
                thread2.get();
                thread1.get();
                Preconditions.checkState(a <= 1, "map : %s",map);
            } catch (IllegalStateException ex) {
                log.error(ex.getMessage());
            }
            a = 0;
            map = new ConcurrentHashMap<>();
        }
        es.shutdown();
    }
}

即使用了線程安全的類,你也不一定能寫出線程安全的代碼。
這個例子概率性的a被加了2次。
原因:是調用客戶端語句不是原子操作。

5、實例變量不正確發布+不同的鎖

package com.tinygao.thread.unsafe;

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

/**
 * Created by gsd on 2017/2/4.
 */
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;
    }
}

鎖錯了,就跟沒鎖一樣。當然上面這個代碼沒有封裝好。
假設new ListHelper() 為a
線程a: a.putifAbsent獲得的是a這個對象的鎖
線程b: a.putifAbsent獲得的是a這個對象的鎖
這兩個沒有問題,他們先同步的,會串行完成,不會造成線程不安全問題。

但。線程c: a.list.put(x)
線程c獲得了a.list.put這個方法鎖住的是 list對象。
所以線程a和線程c鎖的對象不一樣,就會有線程安全問題了。

6、神奇的while

package com.tinygao.thread.unsafe;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

import lombok.extern.slf4j.Slf4j;

/**
 * Created by gsd on 2017/2/5.
 */
@Slf4j
public class VolatileTest {
    public static boolean asleep = false;
    public static long num = 0;

    public static void service() {
        while(!asleep) {
            num++;
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService es = Executors.newFixedThreadPool(1);
        es.submit(VolatileTest::service);
        TimeUnit.MILLISECONDS.sleep(100);
        asleep = true;
        
        log.info("sleep1 ~~~~~~ num: {}", num);
        num = 42;
        TimeUnit.SECONDS.sleep(1);
        log.info("sleep2 ~~~~~~ num: {}", num);
        TimeUnit.SECONDS.sleep(1);
        log.info("sleep3 ~~~~~~ num: {}", num);
        es.shutdown();
        es.awaitTermination(1, TimeUnit.DAYS);
    }
}

在代碼中,即時主線程設置了asleep=true,但是子線程中的while還是停不下來。
在《effective java中文版》——第二版中p230提到:

while(!done)
   i++;

轉成這樣了:

if(!done) {
   while(true)
   i++;
}

這個是為了提升性能,帶來的指令重排。工作內存的數據沒有即使同步到主存中。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,860評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,128評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,025評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,421評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,642評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,177評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,970評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,157評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,410評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,896評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,157評論 2 375

推薦閱讀更多精彩內容

  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,329評論 11 349
  • 1. Java基礎部分 基礎部分的順序:基本語法,類相關的語法,內部類的語法,繼承相關的語法,異常的語法,線程的語...
    子非魚_t_閱讀 31,726評論 18 399
  • 第三章 Java內存模型 3.1 Java內存模型的基礎 通信在共享內存的模型里,通過寫-讀內存中的公共狀態進行隱...
    澤毛閱讀 4,374評論 2 22
  • 一.線程安全性 線程安全是建立在對于對象狀態訪問操作進行管理,特別是對共享的與可變的狀態的訪問 解釋下上面的話: ...
    黃大大吃不胖閱讀 860評論 0 3
  • 老子曾說:“天下難事,必做于易;天下大事,必做于細”,它精辟地指出了想成就一番事業,必須從簡單的事情做起,從細微之...
    豆瓣丶閱讀 308評論 1 0