【多線程問題的解決】

需要回顧之前博文《多線程的問題》

一、 CPU對于兩個冒險的解決辦法

  • 結(jié)構(gòu)冒險(CPU對某一個存儲器讀取資源為例):
    使用同步,依賴硬件提供同步指令。
  • 數(shù)據(jù)冒險:

二、多線程對問題的解決辦法

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

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

<b>??!因為它具備4個特性。永遠記住這四點,絕大部分只要這4點??!</b>

  • <b>有可變的狀態(tài)(以下三個地方代表類是有狀態(tài)的特征)</b>
    1、有類變量
    2 、有實例變量(本例子中的value屬于這類)
    3、有其他對象的引用(比如map.entry對象)
    </br>
  • <b>復合操作(value++包含三個原子操作)</b>
    1、讀取value
    2、value+1計算
    3、將value寫入主存
    </br>

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

  • <b>狀態(tài)不可見</b>
    A線程中value值的改變,對B來說不可見。線程棧內(nèi)存數(shù)據(jù)是互相隔離的,看不到!

  • 為什么不安全之提問

1、沒有狀態(tài)的類,是線程安全的 (√)
2、只要使用線程安全的類寫出來的代碼塊一定是線程安全的(×)
==>多個安全類在一起成了復合操作了,加上沒有控制順序的手段,可能會出現(xiàn)不可預測的結(jié)果。
3、不可變對象一定是線程安全的(√)
==>什么是不可變對象?
1、對象創(chuàng)建之后其狀態(tài)就不能修改。
2、對象的所有狀態(tài)都是final類型
3、對象是正確創(chuàng)建的(this引用沒有逸出)

↓對于第二問看concurrentMap是線程安全的,用了他的代碼塊可不是線程安全的,來舉個栗子吧↓

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());
        });
    }
}

  • 我們的本意:當?shù)谝粋€線程判斷不存在mapkey的時候去填充這個key的值,之后的其他線程只要從map get出來這個key就可以了。
  • 事實:兩個線程都判斷了mapkey不存在,都各自put了一把到map上。導致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 解決安全問題</b>

對應上面的四個特性取反:
<b>1、去可變狀態(tài)</b>
<b>2、復合操作改成原子操作(記住兩個常見的復合操作)</b>
-- if-then操作(像上面map的例子)
-- 取-讀-寫(像上面value++的例子)
<b>3、控制執(zhí)行順序,即保證有序性</b>
<b>4、若有狀態(tài),則讓狀態(tài)在線程間互相可見</b>
</br>

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

  • 不再是成員變量、實例變量了。比如:把他移入到方法內(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
    再看一個不安全的
```

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 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();
        }
        //預期打印是1,看看實際打印多少?
        log.info("get first {}", unfinal.getCurrentNumber(1));
    });

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

}

看一個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、怎么將復合操作變成原子操作</b>
  - "讀-操作-寫" 使用Atomic類

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類型的變量(但復合操作變量就有問題了)
    3、顯式鎖
    4、原子變量(long和double可能不是原子變量,看處理器架構(gòu))

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




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

概念:管程、信號量、互斥量、同步、阻塞、協(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)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,696評論 18 399
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,554評論 19 139
  • 從三月份找實習到現(xiàn)在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發(fā)崗...
    時芥藍閱讀 42,799評論 11 349
  • 在上篇中,我們已經(jīng)討論過如何去實現(xiàn)一個 Map 了,并且也討論了諸多優(yōu)化點。在下篇中,我們將繼續(xù)討論如何實現(xiàn)一個線...
    一縷殤流化隱半邊冰霜閱讀 7,858評論 5 41
  • 夏盡秋荷霧朦朧, 初秋拂曉微感冷。 難忘夏熱情濃時, 魂如殘荷心已死。
    鄉(xiāng)村的月兒閱讀 426評論 0 0

友情鏈接更多精彩內(nèi)容