Maison  >  Article  >  Java  >  Exemple d'analyse de la stratégie de sécurité des threads Java

Exemple d'analyse de la stratégie de sécurité des threads Java

PHPz
PHPzavant
2023-04-20 19:13:061110parcourir

    1. Objets immuables

    Conditions que les objets immuables doivent remplir

    (1) Une fois l'objet créé, son état ne peut pas être modifié

    (2) Tous les champs de l'objet sont des types finaux

    ( 3) L'objet est créé correctement (lors de la création de l'objet, cette référence n'a pas débordé)

    Pour les objets immuables, vous pouvez voir la classe String dans le JDK

    Mots clés finaux : classe, méthode, variable

    (1) Classe modifiée : cette classe Il ne peut pas être hérité. La classe String et les classes wrapper des types de base (tels que Integer, Long, etc.) sont toutes des types finaux. Les variables membres d'une classe finale peuvent être définies sur le type final selon les besoins, mais toutes les méthodes membres d'une classe finale seront implicitement désignées comme méthodes finales.

    (2) Méthode de modification : La méthode de verrouillage n'est pas modifiée par l'efficacité de la classe héritée ; Remarque : Une méthode privée d'une classe sera implicitement désignée comme méthode finale

    (3) Variables modifiées : variables de type données de base (la valeur ne peut pas être modifiée après son initialisation), variables de type référence (après initialisation, elle ne peut pas pointer à d'autres valeurs) Objet)

    fournit une classe Collections dans le JDK, qui fournit de nombreuses méthodes commençant par unmodifiable, comme suit :

    Collections.unmodifiableXXX : Collection, List, Set, Map...

    Parmi elles, les Collections La méthode .unmodifiableXXX XXX peut être Collection, List, Set, Map... À ce stade, transmettre la Collection, List, Set et Map que nous avons nous-mêmes créés à la méthode Collections.unmodifiableXXX deviendra immuable. À ce stade, si vous modifiez les éléments dans Collection, List, Set ou Map, une exception java.lang.UnsupportedOperationException sera levée.

    Dans Guava de Google, il existe de nombreuses classes commençant par Immutable, comme suit :

    ImmutableXXX, XXX peut être Collection, List, Set, Map...

    Remarque : Pour utiliser Guava de Google, vous devez ajouter les dépendances suivantes dans le package Maven :

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

    2. Fermeture du fil

    (1) Fermeture du fil ad hoc : implémentation du contrôle du programme, pire, ignorer

    (2) Fermeture de la pile : variables locales, pas de problèmes de concurrence

    (3) Fil ThreadLocal Closure : Méthode de fermeture particulièrement bonne

    3. Classes et méthodes d'écriture non sécurisées

    1 StringBuilder -> StringBuffer

    StringBuilder : thread-unsafe

    StringBuffer : thread-unsafe

    L'épissage des chaînes implique Pendant ; opération multithread, utilisez StringBuffer pour implémenter

    Dans une méthode spécifique, définissez un objet d'épissage de chaîne et vous pouvez utiliser StringBuilder pour l'implémenter. Parce que lorsque des variables locales sont définies et utilisées dans une méthode, la pile est fermée. Un seul thread utilisera la variable. Cela n'implique pas le fonctionnement des variables par plusieurs threads.

    2. SimpleDateFormat -> JodaTime

    SimpleDateFormat : Thread-safe L'exemple de code sécurisé est le suivant :

    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");
        //请求总数
        public static int clientTotal = 5000;
        //同时并发执行的线程数
        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);
            }
        }
    }

    Modifiez-le simplement avec le code suivant.

    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 {
        //请求总数
        public static int clientTotal = 5000;
        //同时并发执行的线程数
        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);
            }
        }
    }

    Pour JodaTime, vous devez ajouter le package de dépendances suivant dans Maven :

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

    L'exemple de code est le suivant :

    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 {
        //请求总数
        public static int clientTotal = 5000;
        //同时并发执行的线程数
        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 et d'autres classes de collection Collections ne sont pas sécurisées pour les threads

    .

    4. Vérifiez d'abord, puis Exécution : if(condition(a)){handle(a);}

    Remarque : Cette façon d'écrire n'est pas sécurisée pour les threads ! ! ! ! !

    Deux threads effectuent cette opération en même temps et jugent la condition if en même temps, et la variable a est partagée par les threads. Si les deux threads remplissent la condition if, les deux threads exécuteront l'instruction handle(a). en même temps. Ceci, l'instruction handle(a) peut ne pas être thread-safe.

    Le point dangereux est que dans les deux opérations, même si le processus d'exécution précédent est thread-safe, le processus suivant est également thread-safe, mais l'écart entre les processus d'exécution avant et après n'est pas atomique, par conséquent, il sera provoque également une question d'insécurité des threads.

    Dans le processus réel, lorsque vous rencontrez la classe if(condition(a)){handle(a);}, déterminez si a est partagé par les threads. S'il est partagé par les threads, vous devez verrouiller toute la méthode d'exécution. , ou assurez-vous que les deux opérations (si jugement et exécution de code) avant et après if(condition(a)){handle(a);} sont atomiques.

    4. Sécurité des threads - conteneur synchronisé

    1. ArrayList -> Vector, Stack

    ArrayList : thread non sécurisé ;

    Vector : fonctionnement synchrone, mais des situations de thread non sécurisées et du code non sécurisé peuvent se produire. Un exemple est le suivant :

    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 : hérité de Vector, premier entré, dernier sorti.

    2. HashMap -> HashTable (La clé et la valeur ne peuvent pas être nulles)

    HashMap : thread non sécurisé ;

    HashTable : sécurisé pour les threads. Notez que lors de l'utilisation de HashTable, ni la clé ni la valeur ne peuvent être nulles ; .synchronizedXXX(List, Set, Map)

    Remarque : lorsque vous parcourez la collection, ne mettez pas à jour la collection. Lorsque vous devez supprimer des éléments de la collection, vous pouvez parcourir la collection, marquer les éléments qui doivent être supprimés en premier, puis effectuer l'opération de suppression une fois le parcours de la collection terminé. Par exemple, l'exemple de code suivant :

    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);
        }
    }

    五、线程安全-并发容器J.U.C

    J.U.C表示的是java.util.concurrent报名的缩写。

    1. ArrayList -> CopyOnWriteArrayList

    ArrayList:线程不安全;

    CopyOnWriteArrayList:线程安全;

    写操作时复制,当有新元素添加到CopyOnWriteArrayList数组时,先从原有的数组中拷贝一份出来,然后在新的数组中进行写操作,写完之后再将原来的数组指向到新的数组。整个操作都是在锁的保护下进行的。

    CopyOnWriteArrayList缺点:

    (1)每次写操作都需要复制一份,消耗内存,如果元素特别多,可能导致GC;

    (2)不能用于实时读的场景,适合读多写少的场景;

    CopyOnWriteArrayList设计思想:

    (1)读写分离

    (2)最终一致性

    (3)使用时另外开辟空间,解决并发冲突

    注意:CopyOnWriteArrayList读操作时,都是在原数组上进行的,不需要加锁,写操作时复制,当有新元素添加到CopyOnWriteArrayList数组时,先从原有的集合中拷贝一份出来,然后在新的数组中进行写操作,写完之后再将原来的数组指向到新的数组。整个操作都是在锁的保护下进行的。

    2.HashSet、TreeSet -> CopyOnWriteArraySet、ConcurrentSkipListSet

    CopyOnWriteArraySet:线程安全的,底层实现使用了CopyOnWriteArrayList。

    ConcurrentSkipListSet:JDK6新增的类,支持排序。可以在构造时,自定义比较器,基于Map集合。在多线程环境下,ConcurrentSkipListSet中的contains()方法、add()、remove()、retain()等操作,都是线程安全的。但是,批量操作,比如:containsAll()、addAll()、removeAll()、retainAll()等操作,并不保证整体一定是原子操作,只能保证批量操作中的每次操作是原子性的,因为批量操作中是以循环的形式调用的单步操作,比如removeAll()操作下以循环的方式调用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;
    }

    所以,在执行ConcurrentSkipListSet中的批量操作时,需要考虑加锁问题。

    注意:ConcurrentSkipListSet类不允许使用空元素(null)。

    3. HashMap、TreeMap -> ConcurrentHashMap、ConcurrentSkipListMap

    ConcurrentHashMap:线程安全,不允许空值

    ConcurrentSkipListMap:是TreeMap的线程安全版本,内部是使用SkipList跳表结构实现

    4.ConcurrentSkipListMap与ConcurrentHashMap对比如下

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

    (2)ConcurrentSkipListMap支持更高的并发,对数据的存取时间和线程数几乎无关,也就是说,在数据量一定的情况下,并发的线程数越多,ConcurrentSkipListMap越能体现出它的优势。

    注意:在非对线程下尽量使用TreeMap,另外,对于并发数相对较低的并行程序,可以使用Collections.synchronizedSortedMap,将TreeMap进行包装;对于高并发程序,使用ConcurrentSkipListMap提供更高的并发度;在多线程高并发环境中,需要对Map的键值对进行排序,尽量使用ConcurrentSkipListMap。

    Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

    Déclaration:
    Cet article est reproduit dans:. en cas de violation, veuillez contacter admin@php.cn Supprimer