溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Java線程中的安全策略實(shí)例分析

發(fā)布時(shí)間:2022-05-18 09:14:06 來(lái)源:億速云 閱讀:128 作者:zzz 欄目:開(kāi)發(fā)技術(shù)

這篇文章主要介紹“Java線程中的安全策略實(shí)例分析”,在日常操作中,相信很多人在Java線程中的安全策略實(shí)例分析問(wèn)題上存在疑惑,小編查閱了各式資料,整理出簡(jiǎn)單好用的操作方法,希望對(duì)大家解答”Java線程中的安全策略實(shí)例分析”的疑惑有所幫助!接下來(lái),請(qǐng)跟著小編一起來(lái)學(xué)習(xí)吧!

    一、不可變對(duì)象

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

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

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

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

    對(duì)于不可變對(duì)象,可以參見(jiàn)JDK中的String類(lèi)

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

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

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

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

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

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

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

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

    在Google的Guava中,包含了很多以Immutable開(kāi)頭的類(lèi),如下:

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

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

    <dependency>
        <groupId>com.google.guava</groupId>
        <artifactId>guava</artifactId>
        <version>23.0</version>
    </dependency>

    二、線程封閉

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

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

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

    三、線程不安全類(lèi)與寫(xiě)法

    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中添加如下依賴(lài)包:

    <dependency>
        <groupId>joda-time</groupId>
        <artifactId>joda-time</artifactId>
        <version>2.9</version>
    </dependency>

    示例代碼如下:

    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集合類(lèi)為線程不安全類(lèi)

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

    注意:這種寫(xiě)法是線程不安全的?。。。?!

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

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

    實(shí)際過(guò)程中,遇到if(condition(a)){handle(a);}類(lèi)的處理時(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<Integer> 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<Integer> v1){
            for(Integer i : v1){
                if(i == 3){
                    v1.remove(i);
                }
            }
        }
        //此方法拋出:java.util.ConcurrentModificationException
        private static void test2(Vector<Integer> v1){
            Iterator<Integer> iterator = v1.iterator();
            while (iterator.hasNext()){
                Integer i = iterator.next();
                if(i == 3){
                    v1.remove(i);
                }
            }
        }
        //正常
        private static void test3(Vector<Integer> v1){
            for(int i = 0; i < v1.size(); i++){
                if(i == 3){
                    v1.remove(i);
                }
            }
        }
        public static void main(String[] args) throws InterruptedException {
            Vector<Integer> 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)名的縮寫(xiě)。

    1. ArrayList -> CopyOnWriteArrayList

    ArrayList:線程不安全;

    CopyOnWriteArrayList:線程安全;

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

    CopyOnWriteArrayList缺點(diǎn):

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

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

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

    (1)讀寫(xiě)分離

    (2)最終一致性

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

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

    2.HashSet、TreeSet -> CopyOnWriteArraySet、ConcurrentSkipListSet

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

    ConcurrentSkipListSet:JDK6新增的類(lèi),支持排序。可以在構(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類(lèi)型中的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í),需要考慮加鎖問(wèn)題。

    注意:ConcurrentSkipListSet類(lèi)不允許使用空元素(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是無(wú)序的;

    (2)ConcurrentSkipListMap支持更高的并發(fā),對(duì)數(shù)據(jù)的存取時(shí)間和線程數(shù)幾乎無(wú)關(guān),也就是說(shuō),在數(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。

    到此,關(guān)于“Java線程中的安全策略實(shí)例分析”的學(xué)習(xí)就結(jié)束了,希望能夠解決大家的疑惑。理論與實(shí)踐的搭配能更好的幫助大家學(xué)習(xí),快去試試吧!若想繼續(xù)學(xué)習(xí)更多相關(guān)知識(shí),請(qǐng)繼續(xù)關(guān)注億速云網(wǎng)站,小編會(huì)繼續(xù)努力為大家?guī)?lái)更多實(shí)用的文章!

    向AI問(wèn)一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI