【多線程問(wèn)題的解決】

需要回顧之前博文《多線程的問(wèn)題》

一稚新、 CPU對(duì)于兩個(gè)冒險(xiǎn)的解決辦法

  • 結(jié)構(gòu)冒險(xiǎn)(CPU對(duì)某一個(gè)存儲(chǔ)器讀取資源為例):
    使用同步既绩,依賴硬件提供同步指令类咧。
  • 數(shù)據(jù)冒險(xiǎn):

二障簿、多線程對(duì)問(wèn)題的解決辦法

  • <b>2.1 安全問(wèn)題的代碼</b>
 package com.tinygao.thread.safe;
public class UnSafe {
    private int value;

    public int getNext() {
        return value++;
    }
}
  • <b>2.2 為什么不安全</b>
A/B代表兩個(gè)不同線程霹抛,不安全執(zhí)行錯(cuò)誤情況

<b>!卷谈!因?yàn)樗邆?個(gè)特性杯拐。永遠(yuǎn)記住這四點(diǎn),絕大部分只要這4點(diǎn)J勒帷端逼!</b>

  • <b>有可變的狀態(tài)(以下三個(gè)地方代表類(lèi)是有狀態(tài)的特征)</b>
    1、有類(lèi)變量
    2 污淋、有實(shí)例變量(本例子中的value屬于這類(lèi))
    3顶滩、有其他對(duì)象的引用(比如map.entry對(duì)象)
    </br>
  • <b>復(fù)合操作(value++包含三個(gè)原子操作)</b>
    1、讀取value
    2寸爆、value+1計(jì)算
    3礁鲁、將value寫(xiě)入主存
    </br>

  • <b>順序沒(méi)有控制</b>
    B線程沒(méi)有等待A操作完盐欺,就讀取了value。導(dǎo)致執(zhí)行兩次加法仅醇,但最終結(jié)果都是10
    </br>

  • <b>狀態(tài)不可見(jiàn)</b>
    A線程中value值的改變冗美,對(duì)B來(lái)說(shuō)不可見(jiàn)。線程棧內(nèi)存數(shù)據(jù)是互相隔離的析二,看不到粉洼!

  • 為什么不安全之提問(wèn)

1、沒(méi)有狀態(tài)的類(lèi)叶摄,是線程安全的 (√)
2属韧、只要使用線程安全的類(lèi)寫(xiě)出來(lái)的代碼塊一定是線程安全的(×)
==>多個(gè)安全類(lèi)在一起成了復(fù)合操作了,加上沒(méi)有控制順序的手段蛤吓,可能會(huì)出現(xiàn)不可預(yù)測(cè)的結(jié)果宵喂。
3、不可變對(duì)象一定是線程安全的(√)
==>什么是不可變對(duì)象会傲?
1樊破、對(duì)象創(chuàng)建之后其狀態(tài)就不能修改。
2唆铐、對(duì)象的所有狀態(tài)都是final類(lèi)型
3、對(duì)象是正確創(chuàng)建的(this引用沒(méi)有逸出)

↓對(duì)于第二問(wèn)看concurrentMap是線程安全的奔滑,用了他的代碼塊可不是線程安全的艾岂,來(lái)舉個(gè)栗子吧↓

package com.tinygao.thread.safe;

import com.google.common.collect.Maps;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import lombok.extern.slf4j.Slf4j;

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

/**
 * Created by gsd on 2017/1/26.
 */
@Slf4j
public class UnSafe {
    private int num;
    private Map<String, String> map = Maps.newConcurrentMap();

    public int getNumAdd() {
        return num++;
    }

    public String getMapValue() {
        if(!map.containsKey("tinygao")) {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put("tinygao", Thread.currentThread().getName());
        }
        return map.get("tinygao");
    }

    public static void main(String[] args) {
        UnSafe unSafe = new UnSafe();
        ExecutorService es = Executors.newFixedThreadPool(
                2,
               new ThreadFactoryBuilder().setNameFormat("map-%d").build());

        es.submit(()->{
            log.info("map {}",unSafe.getMapValue());
        });
        es.submit(()->{
            log.info("map {}",unSafe.getMapValue());
        });
    }
}

  • 我們的本意:當(dāng)?shù)谝粋€(gè)線程判斷不存在mapkey的時(shí)候去填充這個(gè)key的值,之后的其他線程只要從map get出來(lái)這個(gè)key就可以了朋其。
  • 事實(shí):兩個(gè)線程都判斷了mapkey不存在王浴,都各自put了一把到map上。導(dǎo)致mapkey的值被覆蓋了梅猿。打印結(jié)果(你的可能跟我不一樣):↓
    [map-1] INFO com.tinygao.thread.safe.UnSafe - map map-1
    [map-0] INFO com.tinygao.thread.safe.UnSafe - map map-0

  • <b>2.3 解決安全問(wèn)題</b>

對(duì)應(yīng)上面的四個(gè)特性取反:
<b>1氓辣、去可變狀態(tài)</b>
<b>2、復(fù)合操作改成原子操作(記住兩個(gè)常見(jiàn)的復(fù)合操作)</b>
-- if-then操作(像上面map的例子)
-- 取-讀-寫(xiě)(像上面value++的例子)
<b>3袱蚓、控制執(zhí)行順序钞啸,即保證有序性</b>
<b>4、若有狀態(tài)喇潘,則讓狀態(tài)在線程間互相可見(jiàn)</b>
</br>

<b>2.3.1体斩、怎么去可變狀態(tài)</b>

  • 不再是成員變量、實(shí)例變量了颖低。比如:把他移入到方法內(nèi)部絮吵,這樣就在自己的線程棧中了
 public int getNumAdd() {
        int num = 0;
        return num++;
    }
  • 不在線程之間共享該狀態(tài)(讓狀態(tài)封閉)
private ThreadLocal<Integer> num = new ThreadLocal<>();
   public int getNumAdd() {
       num.set(num.get()+1);
       return num.get();
   }
  • 讓狀態(tài)做到不可變
    final inti num = 1
    再看一個(gè)不安全的
```

package com.tinygao.thread.safe;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
//這個(gè)是不安全的
@Slf4j
public class Unfinal {
private Integer lastNumber;
private Integer currentNumber;

public void setLastNumber(Integer i) {
    this.lastNumber = i;
}

public Integer getCurrentNumber(Integer i) {
    if(lastNumber == null || !lastNumber.equals(i)) {
        return null;
    }
    else {
        return 1;
    }
}

public static void main(String[] args) {
    Unfinal unfinal = new Unfinal();

    ExecutorService es = Executors.newFixedThreadPool(2);
    es.submit(()->{
        unfinal.setLastNumber(1);
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //預(yù)期打印是1,看看實(shí)際打印多少?
        log.info("get first {}", unfinal.getCurrentNumber(1));
    });

    es.submit(()->{
        unfinal.setLastNumber(2);
        //預(yù)期打印是null忱屑,看看實(shí)際打印多少?
        log.info("get seconde {}", unfinal.getCurrentNumber(1));
    });
    es.shutdown();
}

}

看一個(gè)final安全的蹬敲,保證在構(gòu)造函數(shù)中初始化一次狀態(tài)后不可變了

package com.tinygao.thread.safe;
import lombok.extern.slf4j.Slf4j;

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

@Slf4j
public class FinalClass {
private final Integer lastNumber;
private final Integer currentNumber;

public FinalClass(Integer lastNumber, Integer currentNumber) {
    this.lastNumber = lastNumber;
    this.currentNumber = currentNumber;
}

public Integer getCurrentNumber(Integer i) {
    if(lastNumber == null || !lastNumber.equals(i)) {
        return null;
    }
    else {
        return 1;
    }
}

public static void main(String[] args) {
    FinalClass finalclass = new FinalClass(1, 1);

    ExecutorService es = Executors.newFixedThreadPool(2);
    es.submit(()->{
        log.info("get first {}", finalclass.getCurrentNumber(1));
    });

    es.submit(()->{
        log.info("get seconde {}", finalclass.getCurrentNumber(1));
    });
    es.shutdown();
}

}


<b> 2.3.2暇昂、怎么將復(fù)合操作變成原子操作</b>
  - "讀-操作-寫(xiě)" 使用Atomic類(lèi)

private AtomicInteger num2 = new AtomicInteger(0);
public int getNumAdd2() {
return num2.incrementAndGet();
}

  - "if-then" 使用java自帶的原子操作 

private Map<String, String> map = Maps.newConcurrentMap();
public void safeMap() {
map.putIfAbsent("tinygao", Thread.currentThread().getName());
}


<b> 2.3.3、怎么控制執(zhí)行順序</b>
   - 同步
    1伴嗡、synchronized
    2急波、volatile類(lèi)型的變量(但復(fù)合操作變量就有問(wèn)題了)
    3、顯式鎖
    4闹究、原子變量(long和double可能不是原子變量幔崖,看處理器架構(gòu))

<b> 2.3.4、怎么讓狀態(tài)在線程間可見(jiàn)</b>
   - 同步
    1渣淤、synchronized
    2赏寇、volatile類(lèi)型的變量(但復(fù)合操作變量就有問(wèn)題了)
    3、顯式鎖
    4价认、原子變量(long和double可能不是原子變量嗅定,看處理器架構(gòu))




采用互斥。對(duì)于共享資源訪問(wèn)的<b>代碼片段</b>叫做臨界區(qū)
有多種方法用踩,比如鎖變量(0/1)渠退、嚴(yán)格輪換法

概念:管程、信號(hào)量脐彩、互斥量碎乃、同步、阻塞惠奸、協(xié)作梅誓、互斥。
https://zh.wikipedia.org/wiki/%E7%9B%A3%E8%A6%96%E5%99%A8_(%E7%A8%8B%E5%BA%8F%E5%90%8C%E6%AD%A5%E5%8C%96)
198.35.26.96 zh.wikipedia.org
待續(xù)~
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末佛南,一起剝皮案震驚了整個(gè)濱河市梗掰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嗅回,老刑警劉巖及穗,帶你破解...
    沈念sama閱讀 217,084評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異绵载,居然都是意外死亡埂陆,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)娃豹,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)猜惋,“玉大人,你說(shuō)我怎么就攤上這事培愁≈ぃ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 163,450評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵定续,是天一觀的道長(zhǎng)谍咆。 經(jīng)常有香客問(wèn)我禾锤,道長(zhǎng),這世上最難降的妖魔是什么摹察? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,322評(píng)論 1 293
  • 正文 為了忘掉前任恩掷,我火速辦了婚禮,結(jié)果婚禮上供嚎,老公的妹妹穿的比我還像新娘黄娘。我一直安慰自己,他們只是感情好克滴,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評(píng)論 6 390
  • 文/花漫 我一把揭開(kāi)白布逼争。 她就那樣靜靜地躺著,像睡著了一般劝赔。 火紅的嫁衣襯著肌膚如雪誓焦。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,274評(píng)論 1 300
  • 那天着帽,我揣著相機(jī)與錄音杂伟,去河邊找鬼。 笑死仍翰,一個(gè)胖子當(dāng)著我的面吹牛赫粥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播予借,決...
    沈念sama閱讀 40,126評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼越平,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了蕾羊?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,980評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤帽驯,失蹤者是張志新(化名)和其女友劉穎龟再,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體尼变,經(jīng)...
    沈念sama閱讀 45,414評(píng)論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡利凑,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了嫌术。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片哀澈。...
    茶點(diǎn)故事閱讀 39,773評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖度气,靈堂內(nèi)的尸體忽然破棺而出割按,到底是詐尸還是另有隱情,我是刑警寧澤磷籍,帶...
    沈念sama閱讀 35,470評(píng)論 5 344
  • 正文 年R本政府宣布适荣,位于F島的核電站现柠,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏弛矛。R本人自食惡果不足惜够吩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評(píng)論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望丈氓。 院中可真熱鬧周循,春花似錦、人聲如沸万俗。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,713評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)该编。三九已至迄本,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間课竣,已是汗流浹背嘉赎。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,852評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留于樟,地道東北人公条。 一個(gè)月前我還...
    沈念sama閱讀 47,865評(píng)論 2 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像迂曲,于是被迫代替她去往敵國(guó)和親靶橱。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語(yǔ)法路捧,類(lèi)相關(guān)的語(yǔ)法淮逊,內(nèi)部類(lèi)的語(yǔ)法规婆,繼承相關(guān)的語(yǔ)法,異常的語(yǔ)法,線程的語(yǔ)...
    子非魚(yú)_t_閱讀 31,625評(píng)論 18 399
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理霉猛,服務(wù)發(fā)現(xiàn)邪财,斷路器啥刻,智...
    卡卡羅2017閱讀 134,654評(píng)論 18 139
  • 從三月份找實(shí)習(xí)到現(xiàn)在煤搜,面了一些公司,掛了不少凡伊,但最終還是拿到小米零渐、百度、阿里系忙、京東诵盼、新浪、CVTE、樂(lè)視家的研發(fā)崗...
    時(shí)芥藍(lán)閱讀 42,243評(píng)論 11 349
  • 在上篇中,我們已經(jīng)討論過(guò)如何去實(shí)現(xiàn)一個(gè) Map 了杀糯,并且也討論了諸多優(yōu)化點(diǎn)扫俺。在下篇中,我們將繼續(xù)討論如何實(shí)現(xiàn)一個(gè)線...
    一縷殤流化隱半邊冰霜閱讀 7,619評(píng)論 5 41
  • 夏盡秋荷霧朦朧固翰, 初秋拂曉微感冷狼纬。 難忘夏熱情濃時(shí), 魂如殘荷心已死骂际。
    鄉(xiāng)村的月兒閱讀 352評(píng)論 0 0