Heim  >  Artikel  >  Java  >  Beispielanalyse der Java-Thread-Sicherheitsstrategie

Beispielanalyse der Java-Thread-Sicherheitsstrategie

PHPz
PHPznach vorne
2023-04-20 19:13:061105Durchsuche

( 3) Objekt wurde korrekt erstellt (während der Objekterstellung ist diese Referenz nicht übergelaufen)

      Für unveränderliche Objekte können Sie die String-Klasse im JDK sehen
endgültige Schlüsselwörter: Klasse, Methode, Variable

(1) Geänderte Klasse: Diese Klasse kann nicht vererbt werden. Die String-Klasse und die Wrapper-Klasse der Basistypen (z. B. Integer, Long usw.) sind alle endgültige Typen. Mitgliedsvariablen in einer endgültigen Klasse können bei Bedarf auf den endgültigen Typ festgelegt werden, alle Mitgliedsmethoden in einer endgültigen Klasse werden jedoch implizit als endgültige Methoden bezeichnet.

(2) Änderungsmethode: Die Sperrmethode wird durch die geerbte Klasseneffizienz nicht geändert. Hinweis: Eine private Methode einer Klasse wird implizit als endgültige Methode bezeichnet

(3) Modifizierte Variablen: Basisdatentypvariablen (der Wert kann nach der Initialisierung nicht geändert werden), Referenztypvariablen (nach der Initialisierung kann er nicht mehr zeigen). zu anderen Werten) Object)

stellt eine Collections-Klasse im JDK bereit, die viele Methoden bereitstellt, beginnend mit unmodifiable, wie folgt:

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

Darunter die Collections Die .unmodifiableXXX-Methode XXX kann Sammlung, Liste, Satz, Karte sein ... Zu diesem Zeitpunkt wird die Übergabe der Sammlung, Liste, Menge und Karte, die wir selbst erstellt haben, an die Methode Collections.unmodifiableXXX unveränderlich. Wenn Sie zu diesem Zeitpunkt die Elemente in Collection, List, Set oder Map ändern, wird eine java.lang.UnsupportedOperationException-Ausnahme ausgelöst.

In Googles Guava gibt es viele Klassen, die mit Immutable beginnen, wie folgt:

ImmutableXXX, im Maven-Paket:

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

2. Thread-Schließung

(1) Ad-hoc-Thread-Schließung: Programmsteuerungsimplementierung, schlimmstenfalls ignorieren

(2) Stapelschließung: lokale Variablen, keine Parallelitätsprobleme

(3) ThreadLocal-Thread Abschluss: Besonders gute Abschlussmethode

3. Thread-unsichere Klassen und Schreibmethoden

StringBuilder: Thread-unsicher; Multithread-Betrieb, verwenden Sie StringBuffer zum Implementieren

Definieren Sie in einer bestimmten Methode ein String-Spleißobjekt, und Sie können es mit StringBuilder implementieren. Denn wenn lokale Variablen innerhalb einer Methode definiert und verwendet werden, wird der Stapel nur von einem Thread verwendet. Es ist nicht erforderlich, Variablen durch mehrere Threads zu bearbeiten.

2. SimpleDateFormat -> JodaTime

SimpleDateFormat: Thread-safe Das sichere Codebeispiel lautet wie folgt:

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

Ändern Sie es einfach in den folgenden Code.

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

Für JodaTime müssen Sie das folgende Abhängigkeitspaket in Maven hinzufügen:

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

Der Beispielcode lautet wie folgt:

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 und andere Collections-Sammlungsklassen sind threadunsicher

4. Zuerst prüfen und dann ausführen: if(condition(a)){handle(a);}

Hinweis: Diese Schreibweise ist threadunsicher! ! ! ! !

Zwei Threads führen diese Operation gleichzeitig aus und beurteilen gleichzeitig die if-Bedingung. Die a-Variable wird von Threads gemeinsam genutzt. Wenn beide Threads die if-Bedingung erfüllen, führen die beiden Threads die handle(a)-Anweisung aus Gleichzeitig ist die handle(a)-Anweisung möglicherweise nicht threadsicher. Der unsichere Punkt besteht darin, dass bei den beiden Vorgängen, selbst wenn der vorherige Ausführungsprozess threadsicher ist, der nachfolgende Prozess ebenfalls threadsicher ist, die Lücke zwischen den Prozessen vor und nach der Ausführung jedoch nicht atomar ist und daher vorhanden ist verursachen auch Fragen zur Thread-Unsicherheit.

Überlegen Sie im tatsächlichen Prozess, ob a von Threads gemeinsam genutzt wird, wenn Sie auf die Klasse if(condition(a)){handle(a);} stoßen. Wenn es von Threads gemeinsam genutzt wird, müssen Sie die gesamte Ausführungsmethode sperren. , oder stellen Sie sicher, dass die beiden Operationen (bei Beurteilung und Codeausführung) vor und nach if(condition(a)){handle(a);} atomar sind.

4. Thread-Sicherheit – synchronisierter Container

ArrayList: Thread-unsicher;

Vector: synchroner Betrieb, aber Thread-unsichere Situationen und Thread-unsicherer Code können wie folgt aussehen:

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: von Vector geerbt, zuerst rein, zuletzt raus.

2. HashTable (Schlüssel und Wert dürfen nicht null sein)

HashTable: Thread-sicher .synchronizedXXX(List, Set, Map)

Hinweis: Aktualisieren Sie die Sammlung beim Durchlaufen der Sammlung nicht. Wenn Sie Elemente in einer Sammlung löschen müssen, können Sie die Sammlung durchlaufen, zunächst die zu löschenden Elemente markieren und dann den Löschvorgang ausführen, nachdem der Sammlungsdurchlauf abgeschlossen ist. Zum Beispiel der folgende Beispielcode:

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。

Das obige ist der detaillierte Inhalt vonBeispielanalyse der Java-Thread-Sicherheitsstrategie. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Dieser Artikel ist reproduziert unter:yisu.com. Bei Verstößen wenden Sie sich bitte an admin@php.cn löschen