日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

hashmap为什么线程不安全_什么时候线程不安全?怎样做到线程安全?怎么扩展线程安全的类?...

發(fā)布時間:2023/12/13 编程问答 27 豆豆
生活随笔 收集整理的這篇文章主要介紹了 hashmap为什么线程不安全_什么时候线程不安全?怎样做到线程安全?怎么扩展线程安全的类?... 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

本文同名博客老炮說Java:https://www.laopaojava.com/,每天更新Spring/SpringMvc/SpringBoot/實戰(zhàn)項目等文章資料

順便再給大家推薦一套SpringCloud微服務(wù)教程,方便學(xué)習(xí):

SpringCloud微服務(wù)電商項目教程 - 老炮說Java-程序員編程資料和編程經(jīng)驗分享平臺?www.laopaojava.com

教程主要包含下面內(nèi)容:

當(dāng)多個線程去訪問某個類時,如果類會表現(xiàn)出我們預(yù)期出現(xiàn)的行為,那么可以稱這個類是線程安全的。

什么時候會出現(xiàn)線程不安全?

操作并非原子。多個線程執(zhí)行某段代碼,如果這段代碼產(chǎn)生的結(jié)果受不同線程之間的執(zhí)行時序影響,而產(chǎn)生非預(yù)期的結(jié)果,即發(fā)生了競態(tài)條件,就會出現(xiàn)線程不安全;

常見場景:

  • count++。它本身包含三個操作,讀取、修改、寫入,多線程時,由于線程執(zhí)行的時序不同,有可能導(dǎo)致兩個線程執(zhí)行后 count 只加了 1,而原有的目標(biāo)確實希望每次執(zhí)行都加 1;
  • 單例。多個線程可能同時執(zhí)行到instance == null成立,然后新建了兩個對象,而原有目標(biāo)是希望這個對象永遠(yuǎn)只有一個;
  • public MyObj getInstance(){if (instance == null){instance = new MyObj();}return instance}

    解決方式是:當(dāng)前線程在操作這段代碼時,其它線程不能對進(jìn)行操作

    常見方案:

  • 單個狀態(tài)使用 java.util.concurrent.atomic 包中的一些原子變量類,注意如果是多個狀態(tài)就算每個操作是原子的,復(fù)合使用的時候并不是原子的;
  • 加鎖。比如使用 synchronized 包圍對應(yīng)代碼塊,保證多線程之間是互斥的,注意應(yīng)盡可能的只包含在需要作為原子處理的代碼塊上;
  • synchronized 的可重入性

    當(dāng)線程要去獲取它自己已經(jīng)持有的鎖是會成功的,這樣的鎖是可重入的,synchronized 是可重入的
    class Paxi {
    public synchronized void sayHello(){
    System.out.println("hello");
    }
    }
    class MyClass extends Paxi{
    public synchronized void dosomething(){
    System.out.println("do thing ..");
    super.sayHello();
    System.out.println("over");
    }
    }
    它的輸出為
    do thing ..
    hello
    over
    復(fù)制代碼
    • 修改不可見。讀線程無法感知到其它線程寫入的值

    常見場景:

  • 重排序。在沒有同步的情況下,編譯器、處理器以及運(yùn)行時等都有可能對操作的執(zhí)行順序進(jìn)行調(diào)整,即寫的代碼順序和真正的執(zhí)行順序不一樣, 導(dǎo)致讀到的是一個失效的值
  • 讀取 long、double 等類型的變量。JVM 允許將一個 64 位的操作分解成兩個 32 位的操作,讀寫在不同的線程中時,可能讀到錯誤的高低位組合
  • 常見方案:

  • 加鎖。所有線程都能看到共享變量的最新值;
  • 使用 Volatile 關(guān)鍵字聲明變量。只要對這個變量產(chǎn)生了寫操作,那么所有的讀操作都會看到這個修改;
  • 注意:Volatile 并不能保證操作的原子性,比如count++操作同樣有風(fēng)險,它僅保證讀取時返回最新的值。使用的好處在于訪問 Volatile 變量并不會執(zhí)行加鎖操作,也就不會阻塞線程。

    不同步的情況下如何做到線程安全?

  • 線程封閉。即僅在單線程內(nèi)訪問數(shù)據(jù),線程封閉技術(shù)有以下幾種:
    • Ad-hoc 線程封閉。即靠自己寫程序來實現(xiàn),比如保證程序只在單線程上對 volatile 進(jìn)行 讀取-修改-寫入
    • 棧封閉。所有的操作都反生執(zhí)行線程的棧中,比如在方法中的一個局部變量
    • ThreadLocal 類。內(nèi)部維護(hù)了每個線程和變量的一個獨(dú)立副本
  • 只讀共享。即使用不可變的對象。
    • 使用 final 去修飾字段,這樣這個字段的 “值” 是不可改變的
    注意 final 如果修飾的是一個對象引用,比如 set, 它本身包含的值是可變的
    • 創(chuàng)建一個不可變的類,來包含多個可變的數(shù)據(jù)。
    class OneValue{//創(chuàng)建不可變對象,創(chuàng)建之后無法修改,事實上這里也沒有提供修改的方法private final BigInteger last;private final BigInteger[] lastfactor;public OneValue(BigInteger i,BigInteger[] lastfactor){this.last=i;this.lastfactor=Arrays.copy(lastfactor,lastfactor.length);}public BigInteger[] getF(BigInteger i){if(last==null || !last.equals(i)){return null;}else{return Arrays.copy(lastfactor,lastfactor.length)}}}class MyService {//volatile使得cache一經(jīng)更改,就能被所有線程感知到private volatile OneValue cache=new OneValue(null,null); public void handle(BigInteger i){BigInteger[] lastfactor=cache.getF(i);if(lastfactor==null){lastfactor=factor(i);//每次都封裝最新的值cache=new OneValue(i,lastfactor)}nextHandle(lastfactor)}}

    如何構(gòu)造線程安全的類?

  • 實例封閉。將一個對象封裝到另一個對象中,這樣能夠訪問被封裝對象的所有代碼路徑都是已知的,通過合適的加鎖策略可以確保被封裝對象的訪問是線程安全的。
  • java 中的 Collections.synchronizedList 使用的原理就是這樣。部分代碼為
    public static <T> List<T> synchronizedList(List<T> list) {
    return (list instanceof RandomAccess ?
    new SynchronizedRandomAccessList<>(list) :
    new SynchronizedList<>(list));
    }
    復(fù)制代碼

    SynchronizedList 的實現(xiàn), 注意此處用到的 mutex 是內(nèi)置鎖

    static class SynchronizedList<E>
    extends SynchronizedCollection<E>
    implements List<E> {
    private static final long serialVersionUID = -7754090372962971524L;
    final List<E> list;
    public E get(int index) {
    synchronized (mutex) {return list.get(index);}
    }
    public E set(int index, E element) {
    synchronized (mutex) {return list.set(index, element);}
    }
    public void add(int index, E element) {
    synchronized (mutex) {list.add(index, element);}
    }
    public E remove(int index) {
    synchronized (mutex) {return list.remove(index);}
    }
    }
    復(fù)制代碼

    mutex 的實現(xiàn)

    static class SynchronizedCollection<E> implements Collection<E>, >Serializable {private static final long serialVersionUID = 3053995032091335093L;final Collection<E> c; // Backing Collectionfinal Object mutex; // Object on which to synchronizeSynchronizedCollection(Collection<E> c) {if (c==null)throw new NullPointerException();this.c = c;mutex = this; // mutex實際上就是對象本身}

    什么是監(jiān)視器模式

    java 的監(jiān)視器模式,將對象所有可變狀態(tài)都封裝起來,并由對象自己的內(nèi)置鎖來保護(hù), 即是一種實例封閉。比如 HashTable 就是運(yùn)用的監(jiān)視器模式。它的 get 操作就是用的 synchronized,內(nèi)置鎖,來實現(xiàn)的線程安全

    public synchronized V get(Object key) {Entry tab[] = table;int hash = hash(key);int index = (hash & 0x7FFFFFFF) % tab.length;for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {if ((e.hash == hash) && e.key.equals(key)) {return e.value;}}return null; }
    • 內(nèi)置鎖

    每個對象都有內(nèi)置鎖。內(nèi)置鎖也稱為監(jiān)視器鎖。或者可以簡稱為監(jiān)視器
    線程執(zhí)行一個對象的用 synchronized 修飾的方法時,會自動的獲取這個對象的內(nèi)置鎖,方法返回時自動釋放內(nèi)置鎖,執(zhí)行過程中就算拋出異常也會自動釋放。
    以下兩種寫法等效:

    synchronized void myMethdo(){//do something } void myMethdo(){ synchronized(this){//do somthding} } 官方文檔
    • 私有鎖
    public class PrivateLock{private Object mylock = new Object(); //私有鎖void myMethod(){synchronized(mylock){//do something}} }

    它也可以用來保護(hù)對象,相對內(nèi)置鎖,優(yōu)勢在于私有鎖可以有多個,同時可以讓客戶端代碼顯示的獲取私有鎖

    • 類鎖

    在 staic 方法上修飾的,一個類的所有對象共用一把鎖

  • 把線程安全性委托給線程安全的類
  • 如果一個類中的各個組件都是線程安全的,該類是否要處理線程安全問題?

    視情況而定。

  • 只有單個組件,且它是線程安全的。
  • public class DVT{private final ConcurrentMap<String,Point> locations;private final Map<String,Point> unmodifiableMap;public DVT(Map<String,Point> points){locations=new ConcurrentHashMap<String,Point>(points);unmodifiableMap=Collections.unmodifiableMap(locations);}public Map<String,Point> getLocations(){return unmodifiableMap;}public Point getLocation(String id){return locations.get(id);}public void setLocation(String id,int x,int y){if(locations.replace(id,new Point(x,y))==null){throw new IllegalArgumentException("invalid "+id);}}}public class Point{public final int x,y;public Point(int x,int y){this.x=x;this.y=y;}}

    線程安全性分析

    • Point 類本身是無法更改的,所以它是線程安全的,DVT 返回的 Point 方法也是線程安全的
    • DVT 的方法 getLocations 返回的對象是不可修改的,是線程安全的
    • setLocation 實際操作的是 ConcurrentHashMap 它也是線程安全的

    綜上,DVT 的安全交給了‘locations’,它本身是線程安全的,DVT 本身雖沒有任何顯示的同步,也是線程安全。這種情況下,就是 DVT 的線程安全實際是委托給了‘locations’, 整個 DVT 表現(xiàn)出了線程安全。

  • 線程安全性委托給了多個狀態(tài)變量
    只要多個狀態(tài)變量之間彼此獨(dú)立,組合的類并不會在其包含的多個狀態(tài)變量上增加不變性。依賴的增加則無法保證線程安全
  • public class NumberRange{ private final AtomicInteger lower = new AtomicInteger(0); private final AtomicInteger upper = new AtomicInteger(0);public void setLower(int i){//先檢查后執(zhí)行,存在隱患if (i>upper.get(i)){throw new IllegalArgumentException('can not ..');}lower.set(i);}public void setUpper(int i){//先檢查后執(zhí)行,存在隱患if(i<lower.get(i)){throw new IllegalArgumentException('can not ..');}upper.set(i);}}

    setLower 和 setUpper 都是‘先檢查后執(zhí)行’的操作,但是沒有足夠的加鎖機(jī)制保證操作的原子性。假設(shè)原始范圍是 (0,10), 一個線程調(diào)用 setLower(5), 一個設(shè)置 setUpper(4) 錯誤的執(zhí)行時序?qū)⒖赡軐?dǎo)致結(jié)果為(5,4)

    如何對現(xiàn)有的線程安全類進(jìn)行擴(kuò)展?

    假設(shè)需要擴(kuò)展的功能為 ‘沒有就添加’。
  • 直接修改原有的代碼。但通常沒有辦法修改源代碼
  • 繼承。繼承原有的代碼,添加新的功能。但是同步策略保存在兩份文件中,如果底層同步策略變更,很容易出問題
  • 組合。將類放入一個輔助類中,通過輔助類的操作代碼。比如擴(kuò)展 Collections.synchronizedList。期間需要注意鎖的機(jī)制,錯誤方式為
  • 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;}}

    這里的 putIfAbsent 并不能帶來線程安全,原因是 list 的內(nèi)置鎖并不是 ListHelper, 也就是 putIfAbsent 相對 list 的其它方法并不是原子的。Collections.synchronizedList 是鎖在 list 本身的,正確方式為

    public boolean putIfAbsent(E x){synchronized(list){boolean absent = !list.contains(x);if(absent){list.add(x);}return absent;} }

    另外可以不管要操作的類是否是線程安全,對類統(tǒng)一添加一層額外的鎖。實現(xiàn)參考 Collections.synchronizedList 方法

    作者:爬蜥https://juejin.im/post/5b7d68f66fb9a019d80a9002

    總結(jié)

    以上是生活随笔為你收集整理的hashmap为什么线程不安全_什么时候线程不安全?怎样做到线程安全?怎么扩展线程安全的类?...的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。