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

一芝薇、實例變量+復合操作(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

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

二、成員變量+復合操作(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日志俱饿。
單例模式創(chuàng)建了兩個對象阀捅。原因與上一個例子一樣陨溅。

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、實例變量不正確發(fā)布+不同的鎖

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++;
}

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末磨确,一起剝皮案震驚了整個濱河市沽甥,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌乏奥,老刑警劉巖摆舟,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡恨诱,警方通過查閱死者的電腦和手機媳瞪,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來照宝,“玉大人蛇受,你說我怎么就攤上這事〔蘧椋” “怎么了兢仰?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長剂碴。 經常有香客問我把将,道長,這世上最難降的妖魔是什么忆矛? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任察蹲,我火速辦了婚禮,結果婚禮上催训,老公的妹妹穿的比我還像新娘洽议。我一直安慰自己,他們只是感情好漫拭,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布亚兄。 她就那樣靜靜地躺著,像睡著了一般采驻。 火紅的嫁衣襯著肌膚如雪苇羡。 梳的紋絲不亂的頭發(fā)上并徘,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天,我揣著相機與錄音,去河邊找鬼茸俭。 笑死赁炎,一個胖子當著我的面吹牛妇穴,可吹牛的內容都是我干的犬性。 我是一名探鬼主播,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼碎浇,長吁一口氣:“原來是場噩夢啊……” “哼临谱!你這毒婦竟也來了?” 一聲冷哼從身側響起奴璃,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤悉默,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后苟穆,有當地人在樹林里發(fā)現(xiàn)了一具尸體抄课,經...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡唱星,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了跟磨。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片间聊。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖抵拘,靈堂內的尸體忽然破棺而出哎榴,到底是詐尸還是另有隱情,我是刑警寧澤僵蛛,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布尚蝌,位于F島的核電站,受9級特大地震影響充尉,放射性物質發(fā)生泄漏驼壶。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一喉酌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧泵喘,春花似錦泪电、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至鲜锚,卻和暖如春突诬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背芜繁。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工旺隙, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人骏令。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓蔬捷,卻偏偏與公主長得像,于是被迫代替她去往敵國和親榔袋。 傳聞我的和親對象是個殘疾皇子周拐,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350

推薦閱讀更多精彩內容

  • 從三月份找實習到現(xiàn)在,面了一些公司凰兑,掛了不少妥粟,但最終還是拿到小米、百度吏够、阿里勾给、京東滩报、新浪、CVTE锦秒、樂視家的研發(fā)崗...
    時芥藍閱讀 42,218評論 11 349
  • 1. Java基礎部分 基礎部分的順序:基本語法露泊,類相關的語法,內部類的語法旅择,繼承相關的語法惭笑,異常的語法,線程的語...
    子非魚_t_閱讀 31,602評論 18 399
  • 第三章 Java內存模型 3.1 Java內存模型的基礎 通信在共享內存的模型里生真,通過寫-讀內存中的公共狀態(tài)進行隱...
    澤毛閱讀 4,347評論 2 22
  • 一.線程安全性 線程安全是建立在對于對象狀態(tài)訪問操作進行管理沉噩,特別是對共享的與可變的狀態(tài)的訪問 解釋下上面的話: ...
    黃大大吃不胖閱讀 836評論 0 3
  • 老子曾說:“天下難事,必做于易柱蟀;天下大事川蒙,必做于細”,它精辟地指出了想成就一番事業(yè)长已,必須從簡單的事情做起畜眨,從細微之...
    豆瓣丶閱讀 297評論 1 0