欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

一文詳解Java線程中的安全策略

 更新時間:2022年05月17日 16:35:45   作者:冰?河  
Java中的線程到底有哪些安全策略呢?這篇文章將詳細為大家分析一下。文中的示例代碼講解詳細,對我們學習或工作有一定幫助,需要的可以參考一下

一、不可變對象

不可變對象需要滿足的條件

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

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

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

對于不可變對象,可以參見JDK中的String類

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

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

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

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

在JDK中提供了一個Collections類,這個類中提供了很多以unmodifiable開頭的方法,如下:

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

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

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

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

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

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

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

二、線程封閉

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

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

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

三、線程不安全類與寫法

1. StringBuilder -> StringBuffer

StringBuilder:線程不安全;

StringBuffer:線程不安全;

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

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

2. SimpleDateFormat -> JodaTime

SimpleDateFormat:線程不安全,可以將其對象的實例化放入到具體的時間格式化方法中,實現(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");
    //請求總數(shù)
    public static int clientTotal = 5000;
    //同時并發(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 {
    //請求總數(shù)
    public static int clientTotal = 5000;
    //同時并發(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);
        }
    }
}

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

<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 {
    //請求總數(shù)
    public static int clientTotal = 5000;
    //同時并發(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);}

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

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

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

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

四、線程安全-同步容器

1. ArrayList -> Vector, Stack

ArrayList:線程不安全;

Vector:同步操作,但是可能會出現(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,先進后出。

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

HashMap:線程不安全;

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

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

注意:在遍歷集合的時候,不要對集合進行更新操作。當需要對集合中的元素進行刪除操作時,可以遍歷集合,先對需要刪除的元素進行標記,集合遍歷結(jié)束后,再進行刪除操作。例如,下面的示例代碼:

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報名的縮寫。

1. ArrayList -> CopyOnWriteArrayList

ArrayList:線程不安全;

CopyOnWriteArrayList:線程安全;

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

CopyOnWriteArrayList缺點:

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

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

CopyOnWriteArrayList設計思想:

(1)讀寫分離

(2)最終一致性

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

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

2.HashSet、TreeSet -> CopyOnWriteArraySet、ConcurrentSkipListSet

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

ConcurrentSkipListSet:JDK6新增的類,支持排序。可以在構(gòu)造時,自定義比較器,基于Map集合。在多線程環(huán)境下,ConcurrentSkipListSet中的contains()方法、add()、remove()、retain()等操作,都是線程安全的。但是,批量操作,比如:containsAll()、addAll()、removeAll()、retainAll()等操作,并不保證整體一定是原子操作,只能保證批量操作中的每次操作是原子性的,因為批量操作中是以循環(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中的批量操作時,需要考慮加鎖問題。

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

3. HashMap、TreeMap -> ConcurrentHashMap、ConcurrentSkipListMap

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

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

4.ConcurrentSkipListMap與ConcurrentHashMap對比如下

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

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

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

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

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

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

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

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

到此這篇關(guān)于一文詳解Java線程中的安全策略的文章就介紹到這了,更多相關(guān)Java線程安全策略內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論