Java高并發(fā)--線程安全策略

Java高并發(fā)--線程安全策略

主要是學(xué)習(xí)慕課網(wǎng)實(shí)戰(zhàn)視頻《Java并發(fā)編程入門與高并發(fā)面試》的筆記

不可變對(duì)象

發(fā)布不可變對(duì)象可保證線程安全。

實(shí)現(xiàn)不可變對(duì)象有哪些要注意的地方坷牛?比如JDK中的String類腌逢。

  • 不提供setter方法(包括修改字段、字段引用到的的對(duì)象等方法)
  • 將所有字段設(shè)置為final怨绣、private
  • 將類修飾為final迎罗,不允許子類繼承捧灰、重寫(xiě)方法钻注÷烨遥可以將構(gòu)造函數(shù)設(shè)為private,通過(guò)工廠方法創(chuàng)建幅恋。
  • 如果類的字段是對(duì)可變對(duì)象的引用膘掰,不允許修改被引用對(duì)象。 1)不提供修改可變對(duì)象的方法佳遣;2)不共享對(duì)可變對(duì)象的引用识埋。對(duì)于外部傳入的可變對(duì)象,不保存該引用零渐。如要保存可以保存其復(fù)制后的副本窒舟;對(duì)于內(nèi)部可變對(duì)象,不要返回對(duì)象本身诵盼,而是返回其復(fù)制后的副本惠豺。

final關(guān)鍵字可以修飾在類、方法风宁、變量:

  • 類:被修飾的類不能被繼承
  • 方法:被修飾的方法不能被重寫(xiě)
  • 變量:被修飾的是一個(gè)基本類型洁墙,其值不能被修改;被修飾的是一個(gè)對(duì)象引用戒财,這里的“不可變”指不允許其再指向其他對(duì)象热监,但是可以修改對(duì)象里面的值。

關(guān)于上一條中final修飾對(duì)象的引用饮寞。以ArrayList為例

final List<Integer> list= new ArrayList<>();
list.add(3);
list.add(4);
list = new ArrayList<>(); // 編譯時(shí)報(bào)錯(cuò)孝扛,不能再指向其他對(duì)象
list.set(0, 2); // 但是可以修改list里面的值

假如我們就是要求諸如List、Map一類的數(shù)據(jù)結(jié)構(gòu)也不能修改其中的元素呢幽崩?

JDK中Collections的一些靜態(tài)方法提供了支持苦始,如下,舉一個(gè)List的例子

final List<Integer> list= new ArrayList<>();
list.add(3);
list.add(4);
List<Integer> unmodifiableList = Collections.unmodifiableList(list);
System.out.println(unmodifiableList);
unmodifiableList.add(5); // 運(yùn)行時(shí)異常
unmodifiableList.set(0, 2); // 運(yùn)行時(shí)異常

只需要將普通的list傳給Collections.unmodifiableList()作為入?yún)⒓纯伞?/p>

其實(shí)現(xiàn)原理也很簡(jiǎn)單慌申,將普通list中的數(shù)據(jù)拷貝陌选,然后對(duì)于所有添加、修改的操作蹄溉,直接拋出異常即可咨油,這樣就保證了list不能修改其中的元素。

線程安全的問(wèn)題就是出在多個(gè)線程同時(shí)修改共享變量类缤,不可變對(duì)象的策略完全規(guī)避了對(duì)對(duì)象的修改臼勉,所以在多線程中使用也不會(huì)有任何問(wèn)題邻吭。

線程封閉

  • 堆棧封閉:能使用局部變量的地方就不使用全局變量餐弱,多線程下訪問(wèn)同一個(gè)方法時(shí),方法中的局部變量都會(huì)拷貝一份到線程的棧中,也就是說(shuō)每一個(gè)線程中都有只屬于本線程的私有變量膏蚓,因此局部變量不會(huì)被多個(gè)線程共享瓢谢。
  • ThreadLocal:特別好的線程封閉方法,其實(shí)現(xiàn)原理如下

對(duì)于共享變量驮瞧,一般采取同步的方式保證線程安全氓扛。而ThreadLocal是為每一個(gè)線程都提供了一個(gè)線程內(nèi)的局部變量,每個(gè)線程只能訪問(wèn)到屬于它的副本论笔。

下面是set和get的實(shí)現(xiàn)

// set方法
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

// 上面的getMap方法
ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

// get方法
public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

從源碼中可以看出:每一個(gè)線程擁有一個(gè)ThreadLocalMap采郎,這個(gè)map存儲(chǔ)了該線程擁有的所有局部變量。

set時(shí)先通過(guò)Thread.currentThread()獲取當(dāng)前線程狂魔,進(jìn)而獲取到當(dāng)前線程的ThreadLocalMap蒜埋,然后以ThreadLocal自己為key,要存儲(chǔ)的對(duì)象為值最楷,存到當(dāng)前線程的ThreadLocalMap中整份。

get時(shí)也是先獲得當(dāng)前線程的ThreadLocalMap,以ThreadLocal自己為key籽孙,取出和該線程的局部變量烈评。

題話外,一個(gè)線程內(nèi)可以設(shè)置多個(gè)ThreadLocal犯建,這樣該線程就擁有了多個(gè)局部變量讲冠。比如當(dāng)前線程為t1,在t1內(nèi)創(chuàng)建了兩個(gè)ThreadLocal分別是tl1和tl2适瓦,那么t1的ThreadLocalMap就有兩個(gè)鍵值對(duì)沟启。

t1.threadLocals.set(tl1, obj1) // 等價(jià)于在t1線程中調(diào)用tl1.set(obj1)
t1.threadLocals.set(tl2, obj2) // 等價(jià)于在t1線程中調(diào)用tl2.set(obj1)

t1.threadLocals.getEntry(tl1) // 等價(jià)于在t1線程中調(diào)用tl1.get()獲得obj1
t1.threadLocals.getEntry(tl2) // 等價(jià)于在t1線程中調(diào)用tl2.get()獲得obj2

以一個(gè)角色驗(yàn)證的例子為例,為每一個(gè)請(qǐng)求(線程)保存了當(dāng)前訪問(wèn)人的角色犹菇。比如有g(shù)uest和admin德迹。

public class CurrentUserHolder {
    private static final ThreadLocal<String> holder = new ThreadLocal<>();

    public static void setUserHolder(String user) {
        holder.set(user);
    }

    public static String getUserHolder() {
        return holder.get();
    }
}

在進(jìn)行某些敏感操作前,需要對(duì)當(dāng)前請(qǐng)求下的角色進(jìn)行驗(yàn)證揭芍。游客是沒(méi)有訪問(wèn)權(quán)限的胳搞,只有管理員可以。

import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;


@Component
public class AuthService {
    public void checkAccess() {
        // 通過(guò)ThreadLocal取得當(dāng)前線程(請(qǐng)求)中的角色
        String user = CurrentUserHolder.getUserHolder();
        if (!"admin".equals(user)) {
            throw new RuntimeException("操作不被允許称杨!");
        }
    }
}

操作前的驗(yàn)證使用AOP過(guò)濾

import com.shy.aopdemo.security.AuthService;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class AuthAspect {
    @Autowired
    private AuthService authService;
    @Pointcut("execution(* com.shy.aopdemo.service.ProductService.*(..))")
    public void adminOnly() {}

    @Before("adminOnly()")
    public void checkAccess() {
        authService.checkAccess();
    }
}

測(cè)試一下肌毅,如果當(dāng)前請(qǐng)求的角色是guest

import com.shy.aopdemo.security.CurrentUserHolder;
import com.shy.aopdemo.service.ProductService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class AopdemoApplicationTests {
    @Autowired
    private ProductService productService;
    @Test
    public void checkDeleteTest() {
        // 當(dāng)前請(qǐng)求的角色是guest
        CurrentUserHolder.setUserHolder("guest");
        // AOP,在delete之前會(huì)先調(diào)用authService.checkAccess();結(jié)果驗(yàn)證不通過(guò)
        productService.delete(1L);
    }
}

總結(jié)

安全共享對(duì)象的策略

  • 線程限制:一個(gè)被線程限制的對(duì)象姑原,由線程獨(dú)占悬而;只能由它的線程來(lái)修改,例如使用線程內(nèi)的局部變量锭汛、ThreadLocal等
  • 共享只讀:只讀對(duì)象在沒(méi)有額外同步的情況下笨奠,可以被多個(gè)線程并發(fā)訪問(wèn)袭蝗,但是任何線程都無(wú)法修改它。例如般婆,使用不可變對(duì)象(final關(guān)鍵字修飾)
  • 線程安全的對(duì)象:一個(gè)線程按安全的對(duì)象或容器到腥,通過(guò)內(nèi)部的同步機(jī)制來(lái)保證線程安全。比如StringBuffer蔚袍、ConcurrentHashMap乡范、AtomicInteger等線程安全對(duì)象。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末啤咽,一起剝皮案震驚了整個(gè)濱河市晋辆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌宇整,老刑警劉巖栈拖,帶你破解...
    沈念sama閱讀 206,126評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異没陡,居然都是意外死亡涩哟,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,254評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門盼玄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)贴彼,“玉大人,你說(shuō)我怎么就攤上這事埃儿∑髡蹋” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,445評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵童番,是天一觀的道長(zhǎng)精钮。 經(jīng)常有香客問(wèn)我,道長(zhǎng)剃斧,這世上最難降的妖魔是什么轨香? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,185評(píng)論 1 278
  • 正文 為了忘掉前任,我火速辦了婚禮幼东,結(jié)果婚禮上臂容,老公的妹妹穿的比我還像新娘。我一直安慰自己根蟹,他們只是感情好脓杉,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,178評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著简逮,像睡著了一般球散。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上散庶,一...
    開(kāi)封第一講書(shū)人閱讀 48,970評(píng)論 1 284
  • 那天蕉堰,我揣著相機(jī)與錄音凌净,去河邊找鬼。 笑死嘁灯,一個(gè)胖子當(dāng)著我的面吹牛泻蚊,可吹牛的內(nèi)容都是我干的躲舌。 我是一名探鬼主播丑婿,決...
    沈念sama閱讀 38,276評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼没卸!你這毒婦竟也來(lái)了羹奉?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 36,927評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤约计,失蹤者是張志新(化名)和其女友劉穎诀拭,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體煤蚌,經(jīng)...
    沈念sama閱讀 43,400評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡耕挨,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,883評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了尉桩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片筒占。...
    茶點(diǎn)故事閱讀 37,997評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖蜘犁,靈堂內(nèi)的尸體忽然破棺而出翰苫,到底是詐尸還是另有隱情,我是刑警寧澤这橙,帶...
    沈念sama閱讀 33,646評(píng)論 4 322
  • 正文 年R本政府宣布奏窑,位于F島的核電站,受9級(jí)特大地震影響屈扎,放射性物質(zhì)發(fā)生泄漏埃唯。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,213評(píng)論 3 307
  • 文/蒙蒙 一鹰晨、第九天 我趴在偏房一處隱蔽的房頂上張望筑凫。 院中可真熱鬧,春花似錦并村、人聲如沸巍实。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,204評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)棚潦。三九已至,卻和暖如春膝昆,著一層夾襖步出監(jiān)牢的瞬間丸边,已是汗流浹背叠必。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,423評(píng)論 1 260
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留妹窖,地道東北人纬朝。 一個(gè)月前我還...
    沈念sama閱讀 45,423評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像骄呼,于是被迫代替她去往敵國(guó)和親共苛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,722評(píng)論 2 345

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

  • JAVA并發(fā)編程與高并發(fā)解決方案 - 并發(fā)編程 三 相關(guān)文章 JAVA并發(fā)編程與高并發(fā)解決方案 - 并發(fā)編程 一 ...
    chuIllusions丶閱讀 2,750評(píng)論 1 7
  • 一蜓萄、線程狀態(tài)轉(zhuǎn)換新建(New)可運(yùn)行(Runnable)阻塞(Blocking)無(wú)限期等待(Waiting)限期等...
    達(dá)微閱讀 568評(píng)論 1 2
  • 相關(guān)概念 面向?qū)ο蟮娜齻€(gè)特征 封裝,繼承,多態(tài).這個(gè)應(yīng)該是人人皆知.有時(shí)候也會(huì)加上抽象. 多態(tài)的好處 允許不同類對(duì)...
    東經(jīng)315度閱讀 1,925評(píng)論 0 8
  • 由于時(shí)間倉(cāng)促,有些地方未寫(xiě)完,后面會(huì)繼續(xù)補(bǔ)充.如有不妥之處,歡迎及時(shí)與我溝通. 如果你也是在學(xué)習(xí)java,給你們推...
    分不清java閱讀 2,828評(píng)論 0 15
  • 決定寫(xiě)下這篇二寶成長(zhǎng)錄隅茎,主要是因?yàn)樽蛲矶殢?3點(diǎn)哭到1點(diǎn),累困之極嫉沽,好不容易哄睡安靜辟犀,在喂奶抱睡時(shí)忍不住發(fā)...
    一滴水1閱讀 286評(píng)論 0 2