Rumah  >  Artikel  >  Java  >  Contoh analisis strategi keselamatan benang Java

Contoh analisis strategi keselamatan benang Java

PHPz
PHPzke hadapan
2023-04-20 19:13:061069semak imbas

    1. Objek tidak berubah

    Syarat yang perlu dipenuhi oleh objek tidak berubah

    (1) Selepas objek dicipta, keadaannya tidak boleh diubah suai

    (2) Semua medan objek adalah jenis akhir

    (3) Objek dicipta dengan betul (semasa penciptaan objek, rujukan ini tidak melimpah)

    Untuk Objek kebolehubah, sila rujuk kelas String dalam JDK

    kata kunci akhir: kelas, kaedah, pembolehubah

    (1) Kelas diubah suai: Kelas ini tidak boleh diwarisi, Kelas String, kelas pembalut jenis asas (seperti Integer, Long, dll.) adalah semua jenis akhir. Pembolehubah ahli dalam kelas akhir boleh ditetapkan kepada jenis akhir seperti yang diperlukan, tetapi semua kaedah ahli dalam kelas akhir akan secara tersirat ditetapkan sebagai kaedah akhir.

    (2) Kaedah pengubahsuaian: Kaedah penguncian tidak diubah suai oleh kelas yang diwarisi; Nota: Kaedah persendirian kelas akan secara tersirat ditetapkan sebagai kaedah akhir

    (3) Pembolehubah diubah suai: pembolehubah jenis data asas (nilai tidak boleh diubah suai selepas nilai dimulakan), pembolehubah jenis rujukan (tidak boleh diubah suai selepas pemula) Kemudian tuding pada objek lain)

    menyediakan kelas Koleksi dalam JDK, yang menyediakan banyak kaedah bermula dengan tidak boleh diubah suai, seperti berikut:

    Collections.unmodifiableXXX: Collection, List, Set , Map... Dalam kaedah Collections.unmodifiableXXX, ia menjadi tidak boleh diubah. Pada masa ini, jika anda mengubah suai elemen dalam Koleksi, Senarai, Set atau Peta, pengecualian java.lang.UnsupportedOperationException akan dilemparkan.

    Dalam Guava Google, terdapat banyak kelas bermula dengan Immutable, seperti berikut:

    ImmutableXXX, XXX boleh menjadi Collection, List, Set, Map…

    Nota: Untuk menggunakan Guava Google, anda perlu menambah pakej pergantungan berikut dalam Maven:

    2. Penutupan benang

    (1) Penutupan benang ad-hoc: pelaksanaan kawalan program, paling teruk, abaikan 1. StringBuilder -> StringBuffer

    StringBuilder: benang tidak selamat

    StringBuffer: aksara tidak selamat; penyambungan melibatkan operasi berbilang benang, gunakan StringBuffer untuk melaksanakan

    Dalam kaedah tertentu, tentukan objek penyambungan rentetan, yang boleh dilaksanakan menggunakan StringBuilder. Kerana apabila pembolehubah tempatan ditakrifkan dan digunakan dalam kaedah, timbunan ditutup Hanya satu utas yang akan menggunakan pembolehubah itu tidak melibatkan operasi pembolehubah oleh berbilang utas.

    2. SimpleDateFormat -> JodaTime

    SimpleDateFormat: Thread-unsafe, anda boleh meletakkan instantiasi objeknya ke dalam kaedah pemformatan masa tertentu untuk mencapai keselamatan Thread

    JodaTime: Keselamatan benang

    Contoh kod benang-tidak selamat SimpleDateFormat adalah seperti berikut:

    Hanya ubah suai kepada kod berikut.

    Untuk JodaTime, anda perlu menambah pakej pergantungan berikut dalam Maven:

    Kod sampel adalah seperti berikut:

    3. ArrayList, HashSet , HashMap dan kelas koleksi Collections lain adalah thread-unsafe
    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);
            }
        }
    }

    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);
            }
        }
    }
    4. Semak dahulu dan kemudian laksanakan: if(condition(a)){handle(a); }

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

    Nota: Cara penulisan ini adalah tidak selamat! ! ! ! !

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

    Dua utas melakukan operasi ini pada masa yang sama dan menilai keadaan if pada masa yang sama, dan pembolehubah dikongsi oleh utas Jika kedua-dua utas memenuhi syarat if, kedua-dua utas akan dilaksanakan pemegang pada masa yang sama (a) pernyataan, pada masa ini, penyataan pemegang(a) mungkin tidak selamat untuk benang. Titik tidak selamat ialah dalam kedua-dua operasi, walaupun proses pelaksanaan sebelumnya adalah selamat untuk benang, proses seterusnya juga selamat untuk benang, tetapi jurang antara proses pelaksanaan sebelumnya dan kemudian bukan atom, jadi ia juga akan menyebabkan masalah Thread tidak selamat.

    Dalam proses sebenar, apabila menghadapi pemprosesan kelas if(condition(a)){handle(a);}, pertimbangkan sama ada a dikongsi oleh thread Jika ia dikongsi oleh thread, ia perlu dilaksanakan sepanjang pelaksanaan. Tambahkan kunci pada kaedah, atau pastikan dua operasi (jika penghakiman dan pelaksanaan kod) sebelum dan selepas if(condition(a)){handle(a);} adalah atom. 4. Keselamatan benang - bekas disegerakkan

    1 ArrayList -> tetapi situasi tidak selamat benang mungkin berlaku. Contoh kod benang tidak selamat adalah seperti berikut:

    Timbunan: diwarisi daripada Vektor, masuk pertama, keluar terakhir.

    2. HashMap -> HashTable (Kunci, Nilai tidak boleh batal)

    HashMap: thread tidak selamat; Kunci atau Nilai boleh menjadi batal;

    3 Collections.synchronizedXXX(Senarai, Set, Peta)

    Nota: Apabila melintasi koleksi, jangan kemas kini koleksi. Apabila anda perlu memadamkan elemen dalam koleksi, anda boleh melintasi koleksi, mula-mula tandakan elemen yang perlu dipadamkan, dan kemudian lakukan operasi pemadaman selepas traversal koleksi selesai. Contohnya, kod sampel berikut:

    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。

    Atas ialah kandungan terperinci Contoh analisis strategi keselamatan benang Java. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

    Kenyataan:
    Artikel ini dikembalikan pada:yisu.com. Jika ada pelanggaran, sila hubungi admin@php.cn Padam