摘要:什么時(shí)候會(huì)出現(xiàn)線程不安全操作并非原子。只有單個(gè)組件,且它是線程安全的。這種情況下,就是的線程安全實(shí)際是委托給了整個(gè)表現(xiàn)出了線程安全。
當(dāng)多個(gè)線程去訪問(wèn)某個(gè)類時(shí),如果類會(huì)表現(xiàn)出我們預(yù)期出現(xiàn)的行為,那么可以稱這個(gè)類是線程安全的。
什么時(shí)候會(huì)出現(xiàn)線程不安全?
操作并非原子。多個(gè)線程執(zhí)行某段代碼,如果這段代碼產(chǎn)生的結(jié)果受不同線程之間的執(zhí)行時(shí)序影響,而產(chǎn)生非預(yù)期的結(jié)果,即發(fā)生了競(jìng)態(tài)條件,就會(huì)出現(xiàn)線程不安全;
常見(jiàn)場(chǎng)景:
count++。它本身包含三個(gè)操作,讀取、修改、寫(xiě)入,多線程時(shí),由于線程執(zhí)行的時(shí)序不同,有可能導(dǎo)致兩個(gè)線程執(zhí)行后count只加了1,而原有的目標(biāo)確實(shí)希望每次執(zhí)行都加1;
單例。多個(gè)線程可能同時(shí)執(zhí)行到instance == null成立,然后新建了兩個(gè)對(duì)象,而原有目標(biāo)是希望這個(gè)對(duì)象永遠(yuǎn)只有一個(gè);
public MyObj getInstance(){ if (instance == null){ instance = new MyObj(); } return instance }解決方式是:當(dāng)前線程在操作這段代碼時(shí),其它線程不能對(duì)進(jìn)行操作
常見(jiàn)方案:
單個(gè)狀態(tài)使用 java.util.concurrent.atomic包中的一些原子變量類,注意如果是多個(gè)狀態(tài)就算每個(gè)操作是原子的,復(fù)合使用的時(shí)候并不是原子的;
加鎖。比如使用 synchronized 包圍對(duì)應(yīng)代碼塊,保證多線程之間是互斥的,注意應(yīng)盡可能的只包含在需要作為原子處理的代碼塊上;
synchronized的可重入性當(dāng)線程要去獲取它自己已經(jīng)持有的鎖是會(huì)成功的,這樣的鎖是可重入的,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
修改不可見(jiàn)。讀線程無(wú)法感知到其它線程寫(xiě)入的值
常見(jiàn)場(chǎng)景:
重排序。在沒(méi)有同步的情況下,編譯器、處理器以及運(yùn)行時(shí)等都有可能對(duì)操作的執(zhí)行順序進(jìn)行調(diào)整,即寫(xiě)的代碼順序和真正的執(zhí)行順序不一樣,導(dǎo)致讀到的是一個(gè)失效的值
讀取long、double等類型的變量。JVM允許將一個(gè)64位的操作分解成兩個(gè)32位的操作,讀寫(xiě)在不同的線程中時(shí),可能讀到錯(cuò)誤的高低位組合
常見(jiàn)方案:
不同步的情況下如何做到線程安全?加鎖。所有線程都能看到共享變量的最新值;
使用Volatile關(guān)鍵字聲明變量。只要對(duì)這個(gè)變量產(chǎn)生了寫(xiě)操作,那么所有的讀操作都會(huì)看到這個(gè)修改;
注意:Volatile并不能保證操作的原子性,比如count++操作同樣有風(fēng)險(xiǎn),它僅保證讀取時(shí)返回最新的值。使用的好處在于訪問(wèn)Volatile變量并不會(huì)執(zhí)行加鎖操作,也就不會(huì)阻塞線程。
線程封閉。即僅在單線程內(nèi)訪問(wèn)數(shù)據(jù),線程封閉技術(shù)有以下幾種:
Ad-hoc線程封閉。即靠自己寫(xiě)程序來(lái)實(shí)現(xiàn),比如保證程序只在單線程上對(duì)volatile進(jìn)行 讀取-修改-寫(xiě)入
棧封閉。所有的操作都反生執(zhí)行線程的棧中,比如在方法中的一個(gè)局部變量
ThreadLocal類。內(nèi)部維護(hù)了每個(gè)線程和變量的一個(gè)獨(dú)立副本
只讀共享。即使用不可變的對(duì)象。
使用final去修飾字段,這樣這個(gè)字段的“值”是不可改變的
注意final如果修飾的是一個(gè)對(duì)象引用,比如set,它本身包含的值是可變的
創(chuàng)建一個(gè)不可變的類,來(lái)包含多個(gè)可變的數(shù)據(jù)。
class OneValue{ //創(chuàng)建不可變對(duì)象,創(chuàng)建之后無(wú)法修改,事實(shí)上這里也沒(méi)有提供修改的方法 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)造線程安全的類?
實(shí)例封閉。將一個(gè)對(duì)象封裝到另一個(gè)對(duì)象中,這樣能夠訪問(wèn)被封裝對(duì)象的所有代碼路徑都是已知的,通過(guò)合適的加鎖策略可以確保被封裝對(duì)象的訪問(wèn)是線程安全的。
java中的Collections.synchronizedList使用的原理就是這樣。部分代碼為
public staticList synchronizedList(List list) { return (list instanceof RandomAccess ? new SynchronizedRandomAccessList<>(list) : new SynchronizedList<>(list)); }
SynchronizedList的實(shí)現(xiàn),注意此處用到的mutex是內(nèi)置鎖
static class SynchronizedListextends SynchronizedCollection implements List { private static final long serialVersionUID = -7754090372962971524L; final List 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);} } }
mutex的實(shí)現(xiàn)
static class SynchronizedCollectionimplements Collection , >Serializable { private static final long serialVersionUID = 3053995032091335093L; final Collection c; // Backing Collection final Object mutex; // Object on which to synchronize SynchronizedCollection(Collection c) { if (c==null) throw new NullPointerException(); this.c = c; mutex = this; // mutex實(shí)際上就是對(duì)象本身 }
把線程安全性委托給線程安全的類
什么是監(jiān)視器模式java的監(jiān)視器模式,將對(duì)象所有可變狀態(tài)都封裝起來(lái),并由對(duì)象自己的內(nèi)置鎖來(lái)保護(hù),即是一種實(shí)例封閉。比如HashTable就是運(yùn)用的監(jiān)視器模式。它的get操作就是用的synchronized,內(nèi)置鎖,來(lái)實(shí)現(xiàn)的線程安全
public synchronized V get(Object key) { Entry tab[] = table; int hash = hash(key); int index = (hash & 0x7FFFFFFF) % tab.length; for (Entrye = tab[index] ; e != null ; e = e.next) { if ((e.hash == hash) && e.key.equals(key)) { return e.value; } } return null; }
內(nèi)置鎖
每個(gè)對(duì)象都有內(nèi)置鎖。內(nèi)置鎖也稱為監(jiān)視器鎖。或者可以簡(jiǎn)稱為監(jiān)視器
線程執(zhí)行一個(gè)對(duì)象的用synchronized修飾的方法時(shí),會(huì)自動(dòng)的獲取這個(gè)對(duì)象的內(nèi)置鎖,方法返回時(shí)自動(dòng)釋放內(nèi)置鎖,執(zhí)行過(guò)程中就算拋出異常也會(huì)自動(dòng)釋放。
以下兩種寫(xiě)法等效:
synchronized void myMethdo(){ //do something } void myMethdo(){ synchronized(this){ //do somthding } }
> [官方文檔](https://docs.oracle.com/javase/tutorial/essential/concurrency/locksync.html)
私有鎖
public class PrivateLock{ private Object mylock = new Object(); //私有鎖 void myMethod(){ synchronized(mylock){ //do something } } }
它也可以用來(lái)保護(hù)對(duì)象,相對(duì)內(nèi)置鎖,優(yōu)勢(shì)在于私有鎖可以有多個(gè),同時(shí)可以讓客戶端代碼顯示的獲取私有鎖
類鎖
在staic方法上修飾的,一個(gè)類的所有對(duì)象共用一把鎖
視情況而定。
只有單個(gè)組件,且它是線程安全的。
public class DVT{ private final ConcurrentMaplocations; private final Map unmodifiableMap; public DVT(Map points){ locations=new ConcurrentHashMap (points); unmodifiableMap=Collections.unmodifiableMap(locations); } public Map 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類本身是無(wú)法更改的,所以它是線程安全的,DVT返回的Point方法也是線程安全的
DVT的方法getLocations返回的對(duì)象是不可修改的,是線程安全的
setLocation實(shí)際操作的是ConcurrentHashMap它也是線程安全的
綜上,DVT的安全交給了‘locations’,它本身是線程安全的,DVT本身雖沒(méi)有任何顯示的同步,也是線程安全。這種情況下,就是DVT的線程安全實(shí)際是委托給了‘locations’,整個(gè)DVT表現(xiàn)出了線程安全。
線程安全性委托給了多個(gè)狀態(tài)變量
只要多個(gè)狀態(tài)變量之間彼此獨(dú)立,組合的類并不會(huì)在其包含的多個(gè)狀態(tài)變量上增加不變性。依賴的增加則無(wú)法保證線程安全
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 setLower和setUpper都是‘先檢查后執(zhí)行’的操作,但是沒(méi)有足夠的加鎖機(jī)制保證操作的原子性。假設(shè)原始范圍是(0,10),一個(gè)線程調(diào)用 setLower(5),一個(gè)設(shè)置setUpper(4)錯(cuò)誤的執(zhí)行時(shí)序?qū)⒖赡軐?dǎo)致結(jié)果為(5,4)如何對(duì)現(xiàn)有的線程安全類進(jìn)行擴(kuò)展?假設(shè)需要擴(kuò)展的功能為 ‘沒(méi)有就添加’。直接修改原有的代碼。但通常沒(méi)有辦法修改源代碼
繼承。繼承原有的代碼,添加新的功能。但是同步策略保存在兩份文件中,如果底層同步策略變更,很容易出問(wèn)題
組合。將類放入一個(gè)輔助類中,通過(guò)輔助類的操作代碼。
比如擴(kuò)展 Collections.synchronizedList。期間需要注意鎖的機(jī)制,錯(cuò)誤方式為public class ListHelper{ public List list=Collections.synchronizedList(new ArrayList ()); ... public synchronized boolean putIfAbsent(E x){ boolean absent = !list.contains(x); if(absent){ list.add(x); } return absent; } } 這里的putIfAbsent并不能帶來(lái)線程安全,原因是list的內(nèi)置鎖并不是ListHelper,也就是putIfAbsent相對(duì)list的其它方法并不是原子的。Collections.synchronizedList是鎖在list本身的,正確方式為
public boolean putIfAbsent(E x){ synchronized(list){ boolean absent = !list.contains(x); if(absent){ list.add(x); } return absent; } }另外可以不管要操作的類是否是線程安全,對(duì)類統(tǒng)一添加一層額外的鎖。 實(shí)現(xiàn)參考Collections.synchronizedList方法
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://m.specialneedsforspecialkids.com/yun/72400.html
摘要:又是金三銀四的時(shí)候,我希望這份面試題能夠祝你一臂之力自我和項(xiàng)目相關(guān)自我介紹你覺(jué)得自己的優(yōu)點(diǎn)是你覺(jué)得自己有啥缺點(diǎn)你有哪些你為什么要離開(kāi)上家公司你上家公司在,我們公司在,離這么遠(yuǎn)為什么要選擇我們這里上家公司的同事和領(lǐng)導(dǎo)是怎么評(píng)價(jià)你的介紹下你的上 又是金三銀四的時(shí)候,我希望這份面試題能夠祝你一臂之力! 自我和項(xiàng)目相關(guān) 1、自我介紹 2、你覺(jué)得自己的優(yōu)點(diǎn)是?你覺(jué)得自己有啥缺點(diǎn)? 3、你有哪些 ...
摘要:以下為大家整理了阿里巴巴史上最全的面試題,涉及大量面試知識(shí)點(diǎn)和相關(guān)試題。的內(nèi)存結(jié)構(gòu),和比例。多線程多線程的幾種實(shí)現(xiàn)方式,什么是線程安全。點(diǎn)擊這里有一套答案版的多線程試題。線上系統(tǒng)突然變得異常緩慢,你如何查找問(wèn)題。 以下為大家整理了阿里巴巴史上最全的 Java 面試題,涉及大量 Java 面試知識(shí)點(diǎn)和相關(guān)試題。 JAVA基礎(chǔ) JAVA中的幾種基本數(shù)據(jù)類型是什么,各自占用多少字節(jié)。 S...
摘要:線程安全的概念什么時(shí)候線程不安全怎樣做到線程安全怎么擴(kuò)展線程安全的類對(duì)線程安全的支持對(duì)線程安全支持有哪些中的線程池的使用與中線程池的生命周期與線程中斷中的鎖中常見(jiàn)死鎖與活鎖的實(shí)例線程同步機(jī)制顯示鎖使用與原理原理剖析原理中的與原理偏向鎖狀態(tài) showImg(https://segmentfault.com/img/bVblUE9?w=1354&h=1660); 線程安全的概念 showI...
摘要:但是單核我們還是要應(yīng)用多線程,就是為了防止阻塞。多線程可以防止這個(gè)問(wèn)題,多條線程同時(shí)運(yùn)行,哪怕一條線程的代碼執(zhí)行讀取數(shù)據(jù)阻塞,也不會(huì)影響其它任務(wù)的執(zhí)行。 1、多線程有什么用?一個(gè)可能在很多人看來(lái)很扯淡的一個(gè)問(wèn)題:我會(huì)用多線程就好了,還管它有什么用?在我看來(lái),這個(gè)回答更扯淡。所謂知其然知其所以然,會(huì)用只是知其然,為什么用才是知其所以然,只有達(dá)到知其然知其所以然的程度才可以說(shuō)是把一個(gè)知識(shí)點(diǎn)...
摘要:目錄介紹問(wèn)題匯總具體問(wèn)題好消息博客筆記大匯總年月到至今,包括基礎(chǔ)及深入知識(shí)點(diǎn),技術(shù)博客,學(xué)習(xí)筆記等等,還包括平時(shí)開(kāi)發(fā)中遇到的匯總,當(dāng)然也在工作之余收集了大量的面試題,長(zhǎng)期更新維護(hù)并且修正,持續(xù)完善開(kāi)源的文件是格式的同時(shí)也開(kāi)源了生活博客,從年 目錄介紹 00.Java問(wèn)題匯總 01.具體問(wèn)題 好消息 博客筆記大匯總【16年3月到至今】,包括Java基礎(chǔ)及深入知識(shí)點(diǎn),Android技...
閱讀 3023·2023-04-26 00:32
閱讀 507·2019-08-30 15:52
閱讀 2114·2019-08-30 15:52
閱讀 3357·2019-08-30 15:44
閱讀 3288·2019-08-30 14:09
閱讀 1423·2019-08-29 15:15
閱讀 3401·2019-08-28 18:12
閱讀 1084·2019-08-26 13:55