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

      網(wǎng)友投稿 677 2025-04-06

      一、不可變對(duì)象


      不可變對(duì)象需要滿足的條件

      (1)對(duì)象創(chuàng)建以后其狀態(tài)就不能修改

      (2)對(duì)象所有域都是final類型

      (3)對(duì)象是正確創(chuàng)建的(在對(duì)象創(chuàng)建期間,this引用沒有溢出)

      對(duì)于不可變對(duì)象,可以參見JDK中的String類

      final關(guān)鍵字:類、方法、變量

      (1)修飾類:該類不能被繼承,String類,基礎(chǔ)類型的包裝類(比如Integer、Long等)都是final類型。final類中的成員變量可以根據(jù)需要設(shè)置為final類型,但是final類中的所有成員方法,都會(huì)被隱式的指定為final方法。

      (2)修飾方法:鎖定方法不被繼承類修改;效率。注意:一個(gè)類的private方法會(huì)被隱式的指定為final方法

      (3)修飾變量:基本數(shù)據(jù)類型變量(數(shù)值被初始化后不能再修改)、引用類型變量(初始化之后則不能再指向其他的對(duì)象)

      在JDK中提供了一個(gè)Collections類,這個(gè)類中提供了很多以u(píng)nmodifiable開頭的方法,如下:

      Collections.unmodifiableXXX: Collection、List、Set、Map…

      其中Collections.unmodifiableXXX方法中的XXX可以是Collection、List、Set、Map…

      【高并發(fā)】線程安全策略

      此時(shí),將我們自己創(chuàng)建的Collection、List、Set、Map,傳遞到Collections.unmodifiableXXX方法中,就變?yōu)椴豢勺兊牧恕4藭r(shí),如果修改Collection、List、Set、Map中的元素就會(huì)拋出java.lang.UnsupportedOperationException異常。

      在Google的Guava中,包含了很多以Immutable開頭的類,如下:

      ImmutableXXX,XXX可以是Collection、List、Set、Map…

      注意:使用Google的Guava,需要在Maven中添加如下依賴包:

      com.google.guava guava 23.0

      二、線程封閉

      (1)Ad-hoc線程封閉:程序控制實(shí)現(xiàn),最糟糕,忽略

      (2)堆棧封閉:局部變量,無并發(fā)問題

      (3)ThreadLocal線程封閉:特別好的封閉方法

      三、線程不安全類與寫法

      1. StringBuilder -> StringBuffer

      StringBuilder:線程不安全;

      StringBuffer:線程不安全;

      字符串拼接涉及到多線程操作時(shí),使用StringBuffer實(shí)現(xiàn)

      在一個(gè)具體的方法中,定義一個(gè)字符串拼接對(duì)象,此時(shí)可以使用StringBuilder實(shí)現(xiàn)。因?yàn)樵谝粋€(gè)方法內(nèi)部定義局部變量進(jìn)行使用時(shí),屬于堆棧封閉,只有一個(gè)線程會(huì)使用變量,不涉及多線程對(duì)變量的操作,使用StringBuilder即可。

      2. SimpleDateFormat -> JodaTime

      SimpleDateFormat:線程不安全,可以將其對(duì)象的實(shí)例化放入到具體的時(shí)間格式化方法中,實(shí)現(xiàn)線程安全

      JodaTime:線程安全

      SimpleDateFormat線程不安全的代碼示例如下:

      package io.binghe.concurrency.example.commonunsafe; import lombok.extern.slf4j.Slf4j; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; @Slf4j public class DateFormatExample { private static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd"); //請(qǐng)求總數(shù) public static int clientTotal = 5000; //同時(shí)并發(fā)執(zhí)行的線程數(shù) public static int threadTotal = 200; public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for(int i = 0; i < clientTotal; i++){ executorService.execute(() -> { try{ semaphore.acquire(); update(); semaphore.release(); }catch (Exception e){ log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); } public static void update(){ try { simpleDateFormat.parse("20191024"); } catch (ParseException e) { log.error("parse exception", e); } } }

      修改成如下代碼即可。

      package io.binghe.concurrency.example.commonunsafe; import lombok.extern.slf4j.Slf4j; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; @Slf4j public class DateFormatExample2 { //請(qǐng)求總數(shù) public static int clientTotal = 5000; //同時(shí)并發(fā)執(zhí)行的線程數(shù) public static int threadTotal = 200; public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for(int i = 0; i < clientTotal; i++){ executorService.execute(() -> { try{ semaphore.acquire(); update(); semaphore.release(); }catch (Exception e){ log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); } public static void update(){ try { SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMdd"); simpleDateFormat.parse("20191024"); } catch (ParseException e) { log.error("parse exception", e); } } }

      對(duì)于JodaTime需要在Maven中添加如下依賴包:

      joda-time joda-time 2.9

      示例代碼如下:

      package io.binghe.concurrency.example.commonunsafe; import lombok.extern.slf4j.Slf4j; import org.joda.time.DateTime; import org.joda.time.format.DateTimeFormat; import org.joda.time.format.DateTimeFormatter; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; @Slf4j public class DateFormatExample3 { //請(qǐng)求總數(shù) public static int clientTotal = 5000; //同時(shí)并發(fā)執(zhí)行的線程數(shù) public static int threadTotal = 200; private static DateTimeFormatter dateTimeFormatter = DateTimeFormat.forPattern("yyyyMMdd"); public static void main(String[] args) throws InterruptedException { ExecutorService executorService = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for(int i = 0; i < clientTotal; i++){ final int count = i; executorService.execute(() -> { try{ semaphore.acquire(); update(count); semaphore.release(); }catch (Exception e){ log.error("exception", e); } countDownLatch.countDown(); }); } countDownLatch.await(); executorService.shutdown(); } public static void update(int i){ log.info("{} - {}", i, DateTime.parse("20191024", dateTimeFormatter)); } }

      3. ArrayList、HashSet、HashMap等Collections集合類為線程不安全類

      4. 先檢查再執(zhí)行:if(condition(a)){handle(a);}

      注意:這種寫法是線程不安全的!!!!!

      兩個(gè)線程同時(shí)執(zhí)行這種操作,同時(shí)對(duì)if條件進(jìn)行判斷,并且a變量是線程共享的,如果兩個(gè)線程均滿足if條件,則兩個(gè)線程會(huì)同時(shí)執(zhí)行handle(a)語句,此時(shí),handle(a)語句就可能不是線程安全的。

      不安全的點(diǎn)在于兩個(gè)操作中,即使前面的執(zhí)行過程是線程安全的,后面的過程也是線程安全的,但是前后執(zhí)行過程的間隙不是原子性的,因此,也會(huì)引發(fā)線程不安全的問題。

      實(shí)際過程中,遇到if(condition(a)){handle(a);}類的處理時(shí),考慮a是否是線程共享的,如果是線程共享的,則需要在整個(gè)執(zhí)行方法上加鎖,或者保證if(condition(a)){handle(a);}的前后兩個(gè)操作(if判斷和代碼執(zhí)行)是原子性的。

      四、線程安全-同步容器

      1. ArrayList -> Vector, Stack

      ArrayList:線程不安全;

      Vector:同步操作,但是可能會(huì)出現(xiàn)線程不安全的情況,線程不安全的代碼示例如下:

      public class VectorExample { private static Vector vector = new Vector<>(); public static void main(String[] args) throws InterruptedException { while (true){ for(int i = 0; i < 10; i++){ vector.add(i); } Thread thread1 = new Thread(new Runnable() { @Override public void run() { for(int i = 0; i < vector.size(); i++){ vector.remove(i); } } }); Thread thread2 = new Thread(new Runnable() { @Override public void run() { for(int i = 0; i < vector.size(); i++){ vector.get(i); } } }); thread1.start(); thread2.start(); } } }

      Stack:繼承自Vector,先進(jìn)后出。

      2. HashMap -> HashTable(Key, Value都不能為null)

      HashMap:線程不安全;

      HashTable:線程安全,注意使用HashTable時(shí),Key, Value都不能為null;

      3. Collections.synchronizedXXX(List、Set、Map)

      注意:在遍歷集合的時(shí)候,不要對(duì)集合進(jìn)行更新操作。當(dāng)需要對(duì)集合中的元素進(jìn)行刪除操作時(shí),可以遍歷集合,先對(duì)需要?jiǎng)h除的元素進(jìn)行標(biāo)記,集合遍歷結(jié)束后,再進(jìn)行刪除操作。例如,下面的示例代碼:

      public class VectorExample3 { //此方法拋出:java.util.ConcurrentModificationException private static void test1(Vector v1){ for(Integer i : v1){ if(i == 3){ v1.remove(i); } } } //此方法拋出:java.util.ConcurrentModificationException private static void test2(Vector v1){ Iterator iterator = v1.iterator(); while (iterator.hasNext()){ Integer i = iterator.next(); if(i == 3){ v1.remove(i); } } } //正常 private static void test3(Vector v1){ for(int i = 0; i < v1.size(); i++){ if(i == 3){ v1.remove(i); } } } public static void main(String[] args) throws InterruptedException { Vector vector = new Vector<>(); vector.add(1); vector.add(2); vector.add(3); //test1(vector); //test2(vector); test3(vector); } }

      五、線程安全-并發(fā)容器J.U.C

      J.U.C表示的是java.util.concurrent報(bào)名的縮寫。

      1. ArrayList -> CopyOnWriteArrayList

      ArrayList:線程不安全;

      CopyOnWriteArrayList:線程安全;

      寫操作時(shí)復(fù)制,當(dāng)有新元素添加到CopyOnWriteArrayList數(shù)組時(shí),先從原有的數(shù)組中拷貝一份出來,然后在新的數(shù)組中進(jìn)行寫操作,寫完之后再將原來的數(shù)組指向到新的數(shù)組。整個(gè)操作都是在鎖的保護(hù)下進(jìn)行的。

      CopyOnWriteArrayList缺點(diǎn):

      (1)每次寫操作都需要復(fù)制一份,消耗內(nèi)存,如果元素特別多,可能導(dǎo)致GC;

      (2)不能用于實(shí)時(shí)讀的場(chǎng)景,適合讀多寫少的場(chǎng)景;

      CopyOnWriteArrayList設(shè)計(jì)思想:

      (1)讀寫分離

      (2)最終一致性

      (3)使用時(shí)另外開辟空間,解決并發(fā)沖突

      注意:CopyOnWriteArrayList讀操作時(shí),都是在原數(shù)組上進(jìn)行的,不需要加鎖,寫操作時(shí)復(fù)制,當(dāng)有新元素添加到CopyOnWriteArrayList數(shù)組時(shí),先從原有的集合中拷貝一份出來,然后在新的數(shù)組中進(jìn)行寫操作,寫完之后再將原來的數(shù)組指向到新的數(shù)組。整個(gè)操作都是在鎖的保護(hù)下進(jìn)行的。

      2.HashSet、TreeSet -> CopyOnWriteArraySet、ConcurrentSkipListSet

      CopyOnWriteArraySet:線程安全的,底層實(shí)現(xiàn)使用了CopyOnWriteArrayList。

      ConcurrentSkipListSet:JDK6新增的類,支持排序。可以在構(gòu)造時(shí),自定義比較器,基于Map集合。在多線程環(huán)境下,ConcurrentSkipListSet中的contains()方法、add()、remove()、retain()等操作,都是線程安全的。但是,批量操作,比如:containsAll()、addAll()、removeAll()、retainAll()等操作,并不保證整體一定是原子操作,只能保證批量操作中的每次操作是原子性的,因?yàn)榕坎僮髦惺且匝h(huán)的形式調(diào)用的單步操作,比如removeAll()操作下以循環(huán)的方式調(diào)用remove()操作。如下代碼所示:

      //ConcurrentSkipListSet類型中的removeAll()方法的源碼 public boolean removeAll(Collection c) { // Override AbstractSet version to avoid unnecessary call to size() boolean modified = false; for (Object e : c) if (remove(e)) modified = true; return modified; }

      所以,在執(zhí)行ConcurrentSkipListSet中的批量操作時(shí),需要考慮加鎖問題。

      注意:ConcurrentSkipListSet類不允許使用空元素(null)。

      3. HashMap、TreeMap -> ConcurrentHashMap、ConcurrentSkipListMap

      ConcurrentHashMap:線程安全,不允許空值

      ConcurrentSkipListMap:是TreeMap的線程安全版本,內(nèi)部是使用SkipList跳表結(jié)構(gòu)實(shí)現(xiàn)

      4.ConcurrentSkipListMap與ConcurrentHashMap對(duì)比如下

      (1)ConcurrentSkipListMap中的Key是有序的,ConcurrentHashMap中的Key是無序的;

      (2)ConcurrentSkipListMap支持更高的并發(fā),對(duì)數(shù)據(jù)的存取時(shí)間和線程數(shù)幾乎無關(guān),也就是說,在數(shù)據(jù)量一定的情況下,并發(fā)的線程數(shù)越多,ConcurrentSkipListMap越能體現(xiàn)出它的優(yōu)勢(shì)。

      注意:在非對(duì)線程下盡量使用TreeMap,另外,對(duì)于并發(fā)數(shù)相對(duì)較低的并行程序,可以使用Collections.synchronizedSortedMap,將TreeMap進(jìn)行包裝;對(duì)于高并發(fā)程序,使用ConcurrentSkipListMap提供更高的并發(fā)度;在多線程高并發(fā)環(huán)境中,需要對(duì)Map的鍵值對(duì)進(jìn)行排序,盡量使用ConcurrentSkipListMap。

      六、安全共享對(duì)象的策略-總結(jié)

      (1)線程限制:一個(gè)被線程限制的對(duì)象,由線程獨(dú)占,并且只能被占有它的線程修改

      (2)共享只讀:一個(gè)共享只讀的對(duì)象,在沒有額外同步的情況下,可以被多個(gè)線程并發(fā)訪問,但是任何線程都不能修改它。

      (3)線程安全對(duì)象:一個(gè)線程安全的對(duì)象或者容器,在內(nèi)部通過同步機(jī)制來保證線程安全,所以其他線程無需額外的同步就可以通過公共接口隨意訪問它

      (4)被守護(hù)對(duì)象:被守護(hù)對(duì)象只能通過獲取特定的鎖來訪問

      好了,今天就到這兒吧,我是冰河,我們下期見~~

      Java JDK JVM 任務(wù)調(diào)度 多線程

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:六十一、Vue中父子組件傳值和組件參數(shù)校驗(yàn)
      下一篇:怎么插入電子簽名(怎么加入電子簽名)
      相關(guān)文章
      亚洲精品综合一二三区在线| 亚洲国产精品第一区二区三区| 亚洲日韩国产一区二区三区| 亚洲中文无码a∨在线观看| 亚洲AV无码专区电影在线观看 | 亚洲白嫩在线观看| 最新国产AV无码专区亚洲| 亚洲成a人在线看天堂无码| 精品韩国亚洲av无码不卡区| 亚洲国产AV无码一区二区三区| 中文字幕 亚洲 有码 在线| 亚洲理论片在线中文字幕| 久久亚洲精品中文字幕| 亚洲最大福利视频网站| 亚洲一区二区成人| 亚洲综合婷婷久久| 亚洲视频一区在线观看| 亚洲黄网站wwwwww| 亚洲一区无码中文字幕乱码| 亚洲香蕉在线观看| 亚洲欧美日韩综合俺去了| 亚洲高清毛片一区二区| 亚洲成熟丰满熟妇高潮XXXXX| 亚洲AV日韩综合一区尤物| 亚洲综合色丁香婷婷六月图片| 7777久久亚洲中文字幕| 亚洲熟女精品中文字幕| 亚洲精品无码av中文字幕| 亚洲Av无码国产一区二区| 亚洲av无码专区在线电影天堂| 亚洲国产精品无码久久| 亚洲AV成人片无码网站| yy6080久久亚洲精品| 亚洲精品456播放| 日本亚洲国产一区二区三区| 亚洲桃色AV无码| 亚洲国产第一页www| 亚洲视频免费一区| 亚洲真人无码永久在线观看| 亚洲av永久无码天堂网| 亚洲精品成人区在线观看|