Die Geschichte der gleichzeitigen Programmierung – das gemeinsame Modell der Parallelität

Gemeinsames Modell der Parallelität


1. Durch Multithreading verursachte Freigabeprobleme

Wenn beispielsweise eine gemeinsam genutzte Ressource von zwei Threads verwendet wird. Thread 1 führt eine +1-Operation für die Variable i=0 aus, wechselt aber auf dem Weg zu +1 zu Thread 2. Thread 2 führt eine -1-Operation für die Variable i aus und gibt dann -1 an die gemeinsam genutzte Variable zurück. Aber dann hat das Zurückschalten zu Thread 1 +1, das heißt, die lokale Variable ist bereits 1. Das erneute Zuweisen des Werts zur gemeinsam genutzten Variablen führt zu Parallelitätsproblemen. Zweitens,
wenn die gemeinsam genutzte Ressource immer von einem Thread verwendet wird, kann dies der Thread sein Verschwendet durch Ruhezustand, Warten, E/A und andere Vorgänge. CPU-Nutzungszeit, dann können Sie diese Zeit anderen geben

Problemanalyse
Der Bytecode von i++ lautet
getstatic i, um die statische Variable i zu erhalten, die das i des Hauptspeichers ist.

iconst_1 bereitet Konstante 1 vor

iadd führt die Addition durch

pustatisch i

Das i++, das wir sehen, ist also keine atomare Anweisung. Da es sich nicht um eine atomare Anweisung handelt, kann der Thread natürlich dazu führen, dass diese Anweisungen während des Umschaltvorgangs verschachtelt werden. Letztendlich sind gemeinsam genutzte Ressourcen ein Problem schmutziger Daten.

Der kritische Abschnitt
bedeutet tatsächlich, dass der Code gemeinsam genutzte Ressourcen enthält, auf die mehrere Threads zugreifen. Daher ist dieser Code der kritische Abschnitt.

Race-Bedingung:
Wenn mehrere Threads in einem kritischen Abschnitt unterschiedliche Ausführungsanweisungssequenzen haben und die Ergebnisse unvorhersehbar sind, handelt es sich um eine Race-Bedingung.

2. Lösung

① Nachdem der Synchronisierungsthread
1 gesperrt wurde, kann Thread 2 die Sperre nicht erhalten und den kritischen Abschnitt nicht ausführen. Thread 2 blockiert und wartet, bis Thread 1 die Freigabe der Sperre abgeschlossen hat, bevor er verwendet werden kann. Synchronize kann mit einem Raum verglichen werden. Nur wer über ein Schloss verfügt, kann den Raum betreten, um Dinge zu erledigen. Selbst wenn die CPU-Zeitscheibe aufgebraucht ist, können andere Threads den Raum nicht betreten, solange kein Schlüssel für das Schloss vorhanden ist. Wenn der Vorgang abgeschlossen ist, wird die Sperre aufgehoben und die blockierten Threads werden aktiviert.
Fügen Sie hier eine Bildbeschreibung ein

static int count=0;
    static Object lock=new Object();
    public static void main(String[] args) throws InterruptedException {
    
    
        Room room = new Room();
        Thread t1 = new Thread(() -> {
    
    
            for (int i = 0; i < 5000; i++) {
    
    
//                room.increment();
                synchronized (lock){
    
    
                    count++;
                }

            }
        }, "t1");

        Thread t2 = new Thread(() -> {
    
    
            for (int i = 0; i < 5000; i++) {
    
    
//                room.decrement();
                synchronized (lock){
    
    
                    count--;
                }

            }
        }, "t2");

        t1.start();
        t2.start();
        t1.join();
        t2.join();
//        log.debug("{}", room.getCounter());
        log.debug("{}",count);
    }

Denken
① Wenn die Synchronisierung außerhalb platziert wird? Tatsächlich wird die Sperre erst freigegeben, nachdem das gesamte for ausgeführt wurde – Atomizität
② Was passiert, wenn t1 und t2 unterschiedliche Objekte verwenden? Dies entspricht dem Betreten verschiedener Räume, dann hat die Sperre keine Wirkung und die beiden Threads führen weiterhin die Codeblöcke im kritischen Abschnitt aus, was zu unterschiedlichen Ausführungssequenzen führt.
③Wenn t1 gesperrt ist, t2 jedoch nicht? Das bedeutet, dass t2 den kritischen Abschnitt jederzeit ausführen kann

Die objektorientierte Verbesserung
besteht tatsächlich darin, alle kritischen Abschnitte und gemeinsam genutzten Ressourcen, die gesperrt werden müssen, in einer Klasse zu kapseln. Das Hinzufügen von „synchonize“ zur Methode entspricht dem Sperren dieser Methode. Wenn es sich um eine statische Methode handelt, entspricht dies dem Sperren der Klasse object.class

class Room {
    
    
    private int counter = 0;

    public synchronized void increment() {
    
    
        counter++;
    }

    public synchronized void decrement() {
    
    
        counter--;
    }

    public synchronized int getCounter() {
    
    
        return counter;
    }
}

3. Synchronisieren Sie die Methode

Acht Thread-Sperren
Fall 1: Wenn kein Ruhezustand vorliegt, sperrt n1 sein eigenes Objekt, wenn die Methoden a und b ausgeführt werden. Sie schließen sich also gegenseitig aus.
Situation 2: Im Ruhezustand ist es immer noch dasselbe. Die beiden Threads, die die Methoden a und b ausführen, sehen, wer zuerst die Sperre erhält. Dann wer es zuerst ausführt. Unabhängig davon, ob sich darin Schlaf befindet, müssen Threads, die die Sperre nicht erhalten haben, warten.

@Slf4j(topic = "c.Test8Locks")
public class Test8Locks {
    
    
    public static void main(String[] args) {
    
    
        Number n1 = new Number();
//        Number n2 = new Number();
        new Thread(() -> {
    
    
            log.debug("begin");
            n1.a();
        }).start();
        new Thread(() -> {
    
    
            log.debug("begin");
            n1.b();
        }).start();
    }
}
@Slf4j(topic = "c.Number")
class Number{
    
    
    public synchronized void a() {
    
    
        sleep(1);
        log.debug("1");
    }
    public synchronized void b() {
    
    
        log.debug("2");
    }
}

Fall 3: In diesem Fall ist c nicht gesperrt, das heißt, es kann nach Belieben ausgeführt werden. Die möglichen Ergebnisse sind 3 12, 32 1 oder 23 1.

@Slf4j(topic = "c.Test8Locks")
public class Test8Locks {
    
    
    public static void main(String[] args) {
    
    
        Number n1 = new Number();
//        Number n2 = new Number();
        new Thread(() -> {
    
    
            log.debug("begin");
            n1.a();
        }).start();
        new Thread(() -> {
    
    
            log.debug("begin");
            n1.b();
        }).start();

        new Thread(() -> {
    
    
            log.debug("begin");
            n1.c();
        }).start();

    }
}
@Slf4j(topic = "c.Number")
class Number{
    
    
    public synchronized void a() {
    
    
        sleep(1);
        log.debug("1");
    }
    public synchronized void b() {
    
    
        log.debug("2");
    }

    public void c(){
    
    
        log.debug("3");
    }
}

Situation 4: Die an sie gebundenen Sperren sind unterschiedlich, was keiner Sperre entspricht. Das Endergebnis muss 21 sein. Weil 1 Thread schläft

@Slf4j(topic = "c.Test8Locks")
public class Test8Locks {
    
    
    public static void main(String[] args) {
    
    
        Number n1 = new Number();
        Number n2 = new Number();
        new Thread(() -> {
    
    
            log.debug("begin");
            n1.a();
        }).start();
        new Thread(() -> {
    
    
            log.debug("begin");
            n2.b();
        }).start();

//        new Thread(() -> {
    
    
//            log.debug("begin");
//            n1.c();
//        }).start();

    }
}
@Slf4j(topic = "c.Number")
class Number{
    
    
    public synchronized void a() {
    
    
        sleep(1);
        log.debug("1");
    }
    public synchronized void b() {
    
    
        log.debug("2");
    }
//
//    public void c(){
    
    
//        log.debug("3");
//    }
}

In den Fällen 5 bis 8 wird statische Aufladung hinzugefügt. Beurteilen Sie daher anhand der von ihnen gesperrten Objekte, ob sie dieselbe Sperre haben.

Viertens die Thread-Sicherheitsanalyse von Variablen

Gibt es Thread-Sicherheitsprobleme mit statischen Variablen und Mitgliedsvariablen?
Wenn es nur ums Lesen geht, spielt es keine Rolle, wenn es um Lesen und Schreiben geht, sollten Sie auf den kritischen Abschnitt achten

Lokale Variablen?
Wenn es sich um einen Referenztyp handelt, dann gibt es einen.
Der Wert lokaler Variablen wird im Stapelrahmen des Threads gespeichert, der privat ist. Anstatt die Variable wie eine statische Variable aus dem Methodenbereich zu entfernen und sie dann entsprechend zu ändern.
Fügen Sie hier eine Bildbeschreibung ein
Situation 1: Die ThreadUnsafe-Liste wird in der Klasse erstellt, was zu Thread-Sicherheitsproblemen führt, die dadurch verursacht werden, dass der Thread dieselbe Liste im Heap verarbeitet.

Fall 2: Fügen Sie die Liste in Methode 1 ein, dann handelt es sich um einen Verweis auf eine lokale Variable, und jeder Thread verfügt nach dem Aufruf der Methode über eine eigene Liste. Dann gibt es keine Thread-Sicherheitsprobleme

Situation 3: Wenn eine Unterklasse diese Methode überschreibt, kann sie auch die Liste abrufen, was dazu führt, dass mehrere Threads die Liste bearbeiten. Die Lösung besteht darin, der Methode final hinzuzufügen, um zu verhindern, dass Unterklassen sie überschreiben.

public class TestThreadSafe {
    
    

    static final int THREAD_NUMBER = 2;
    static final int LOOP_NUMBER = 200;
    public static void main(String[] args) {
    
    
//        ThreadSafeSubClass test = new ThreadSafeSubClass();
        ThreadUnsafe test=new ThreadUnsafe();
        for (int i = 0; i < THREAD_NUMBER; i++) {
    
    
            new Thread(() -> {
    
    
                test.method1(LOOP_NUMBER);
            }, "Thread" + (i+1)).start();
        }
    }
}
class ThreadUnsafe {
    
    
    ArrayList<String> list = new ArrayList<>();
    public void method1(int loopNumber) {
    
    
        for (int i = 0; i < loopNumber; i++) {
    
    
            method2();
            method3();
        }
    }

    private void method2() {
    
    
        list.add("1");
    }

    private void method3() {
    
    
        list.remove(0);
    }
}

class ThreadSafe {
    
    
    public final void method1(int loopNumber) {
    
    
        ArrayList<String> list = new ArrayList<>();
        for (int i = 0; i < loopNumber; i++) {
    
    
            method2(list);
            method3(list);
        }
    }

    public void method2(ArrayList<String> list) {
    
    
        list.add("1");
    }

    private void method3(ArrayList<String> list) {
    
    
        System.out.println(1);
        list.remove(0);
    }
}

class ThreadSafeSubClass extends ThreadSafe{
    
    
//    @Override
    public void method3(ArrayList<String> list) {
    
    
        System.out.println(2);
        new Thread(() -> {
    
    
            list.remove(0);
        }).start();
    }
}

Fügen Sie hier eine Bildbeschreibung ein
Thread-sichere Klassen
Integer
HashTable
String
Random
Vector
Classes unter JUC

Ihre einzelnen Methoden sind threadsicher, bei der Ausführung mehrerer Methoden ist das jedoch anders.
Das Problem mit dem folgenden Code besteht darin, dass Thread 1 nach erfolgreicher Beurteilung wechselt und die Sperre einfach aufhebt. Dann erhält Thread 2 die Sperre zur Beurteilung, wechselt Thread 1 erneut, um die Sperre zu erhalten und verarbeitet den Put, und wechselt Thread 2, um die Sperre zu erhalten und verarbeiten Sie den Put. Weil die Sperre aufgehoben wird, nachdem eine einzelne Methode ausgeführt wurde. Daher muss es noch als Ganzes gesperrt werden, bevor die Verarbeitung fortgesetzt werden kann.
Fügen Sie hier eine Bildbeschreibung ein
Unveränderlicher, threadsicherer
String
String und Integer sind beide unveränderlich. String ist im Wesentlichen ein char[]-Array. Wenn es sich um eine Teilstring-Methode handelt, kopiert sie tatsächlich ein neues Array und weist es dann dem char-Array von String zu. Ersetzen erstellt tatsächlich ein Array und vergleicht es dann mit dem alten Wert des Arrays. Wenn es sich um einen alten Wert handelt, weist es dieser Position im neuen Array direkt einen neuen Wert zu.

public String replace(char oldChar, char newChar) {
    
    
        if (oldChar != newChar) {
    
    
            int len = value.length;
            int i = -1;
            char[] val = value; /* avoid getfield opcode */

            while (++i < len) {
    
    
                if (val[i] == oldChar) {
    
    
                    break;
                }
            }
            if (i < len) {
    
    
                //创建新数组
                char buf[] = new char[len];
                for (int j = 0; j < i; j++) {
    
    
                    buf[j] = val[j];
                }
                while (i < len) {
    
    
                    char c = val[i];
                    //根据原来的数组是旧值的位置改变成新值
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }
                return new String(buf, true);
            }
        }
        return this;
    }
 public String substring(int beginIndex) {
    
    
        if (beginIndex < 0) {
    
    
            throw new StringIndexOutOfBoundsException(beginIndex);
        }
        int subLen = value.length - beginIndex;
        if (subLen < 0) {
    
    
            throw new StringIndexOutOfBoundsException(subLen);
        }
     //实际上就是创建了一个新的String,而不是修改了值
        return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}


  public String(char value[], int offset, int count) {
    
    
        if (offset < 0) {
    
    
            throw new StringIndexOutOfBoundsException(offset);
        }
        if (count <= 0) {
    
    
            if (count < 0) {
    
    
                throw new StringIndexOutOfBoundsException(count);
            }
            if (offset <= value.length) {
    
    
                this.value = "".value;
                return;
            }
        }
        // Note: offset or count might be near -1>>>1.
        if (offset > value.length - count) {
    
    
            throw new StringIndexOutOfBoundsException(offset + count);
        }
      //实际上就是创建新数组并且进行复制
        this.value = Arrays.copyOfRange(value, offset, offset+count);
    }

Beispielanalyse:
Diese Art von Thread ist nicht sicher, da MyServlet gemeinsam genutzt wird und sich UserService im Heap befindet. Seine Methoden können von mehreren Threads aufgerufen werden, um die Anzahl zu ändern, was zu Parallelitätsproblemen führen kann.
Fügen Sie hier eine Bildbeschreibung ein
Dies führt auch zu Parallelitätsproblemen. Die Singleton-Freigabe kann von mehreren Multithreads aufgerufen werden und deckt Variablen wie „Start“ ab.
Fügen Sie hier eine Bildbeschreibung ein
Dies verursacht keine Thread-Sicherheitsprobleme, da keine Variablen geändert werden können.
Fügen Sie hier eine Bildbeschreibung ein
Hier treten Thread-Sicherheitsprobleme auf, da die Verbindung offengelegt ist und von mehreren Threads verarbeitet werden kann. Wenn Thread 1 bei der Verarbeitung von getCon zu Thread 2 wechselt und dieser zufällig geschlossen wird, kann die Verbindung von Thread 1 nicht weiter ausgeführt werden, da sie geändert wurde.
Fügen Sie hier eine Bildbeschreibung ein
Hier besteht kein Thread-Sicherheitsproblem, da ein jedes Mal erstelltes UserDao einer lokalen Variablen entspricht. Es handelt sich nicht um eine gemeinsam genutzte Ressource.
Fügen Sie hier eine Bildbeschreibung ein
Hier können Thread-Sicherheitsprobleme auftreten, da lokale Variablen durch Unterklassenmethoden verfügbar gemacht werden und von Unterklassenobjekten über Threads geändert werden können. Zusammenfassung: Die Thread-
Fügen Sie hier eine Bildbeschreibung ein
Sicherheit hängt davon ab, ob sie geändert werden kann. Gemeinsam genutzte Ressourcen

5. Übungen

Das Thread-Sicherheitsproblem tritt auf, wenn möglicherweise mehrere Threads Tickets verkaufen und die Anzahl der gemeinsam genutzten Variablen gleichzeitig herausgenommen wird. Die endgültige Zählzahl ist der vom letzten Thread verarbeitete Wert. Anstatt Werte gemeinsam zu verarbeiten, wurde der Zählerstand aufgrund eines Fehlers in der Ausführungsreihenfolge von einem anderen Thread eingelesen, ohne auf die Verarbeitung zu warten.

Die Lösung besteht darin, den kritischen Abschnitt zu sperren, bei dem es sich eigentlich um den Verkauf des Fensters handelt. Warum also nicht amountList und window sperren? Der Grund dafür ist, dass sie nicht dieselbe gemeinsam genutzte Ressource betreiben und unterschiedlich damit umgehen, sodass keine Sperre erforderlich ist. Allerdings waren die beiden vorherigen Vorgänge von HashTable, get und put, auf dieselbe gemeinsam genutzte Ressource ausgerichtet, was dazu führte, dass der letzte Wert vom letzten Thread überschrieben wurde. Weil Thread 1 nicht abgeschlossen ist, wenn das leere Urteil erfolgreich ist. Darüber hinaus sind die beiden Vorgänge nicht gesperrt, sondern einer ist abgeschlossen und der andere ist abgeschlossen

@Slf4j(topic = "c.ExerciseSell")
public class ExerciseSell {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        // 模拟多人买票
        TicketWindow window = new TicketWindow(1000);

        // 所有线程的集合
        List<Thread> threadList = new ArrayList<>();
        // 卖出的票数统计
        List<Integer> amountList = new Vector<>();
        for (int i = 0; i < 2000; i++) {
    
    
            Thread thread = new Thread(() -> {
    
    
                // 买票

                try {
    
    
                    Thread.sleep(random(10));
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                int amount = window.sell(random(5));
                // 统计买票数
                amountList.add(amount);
            });
            threadList.add(thread);
            thread.start();
        }

        for (Thread thread : threadList) {
    
    
            thread.join();
        }

        // 统计卖出的票数和剩余票数
        log.debug("余票:{}",window.getCount());
        log.debug("卖出的票数:{}", amountList.stream().mapToInt(i-> i).sum());
    }

    // Random 为线程安全
    static Random random = new Random();

    // 随机 1~5
    public static int random(int amount) {
    
    
        return random.nextInt(amount) + 1;
    }
}

// 售票窗口
class TicketWindow {
    
    
    private int count;

    public TicketWindow(int count) {
    
    
        this.count = count;
    }

    // 获取余票数量
    public int getCount() {
    
    
        return count;
    }

    // 售票 synchronized
    public  int sell(int amount) {
    
    
        if (this.count >= amount) {
    
    
            this.count -= amount;
            return amount;
        } else {
    
    
            return 0;
        }
    }
}

Vector自己的方法就已经带锁

public synchronized boolean add(E e) {
    
    
        modCount++;
        ensureCapacityHelper(elementCount + 1);
        elementData[elementCount++] = e;
        return true;
    }

Übertragungsproblem
Hier treten tatsächlich Thread-Sicherheitsprobleme auf. Die Hauptsache ist, dass, wenn a Geld überweist, auch b Geld überweist. Dann bekommt b definitiv a ohne Überweisung, und das Gleiche gilt für a. Ist es also in Ordnung, die Synchronisierung direkt zur Methode hinzuzufügen? Wenn man ein solches Objekt bindet, funktioniert das natürlich nicht, da es zwei Konten mit unterschiedlichen Schlössern gibt und diese unterschiedliche Räume betreten. Dann besteht die Lösung darin, die einzige Account.class-Klasse zu verwenden, dann kann sie gesperrt werden

@Slf4j(topic = "c.ExerciseTransfer")
public class ExerciseTransfer {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Account a = new Account(1000);
        Account b = new Account(1000);
        Thread t1 = new Thread(() -> {
    
    
            for (int i = 0; i < 1000; i++) {
    
    
                a.transfer(b, randomAmount());
            }
        }, "t1");
        Thread t2 = new Thread(() -> {
    
    
            for (int i = 0; i < 1000; i++) {
    
    
                b.transfer(a, randomAmount());
            }
        }, "t2");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        // 查看转账2000次后的总金额
        log.debug("total:{}", (a.getMoney() + b.getMoney()));
    }

    // Random 为线程安全
    static Random random = new Random();

    // 随机 1~100
    public static int randomAmount() {
    
    
        return random.nextInt(100) + 1;
    }
}

// 账户
class Account {
    
    
    private int money;

    public Account(int money) {
    
    
        this.money = money;
    }

    public int getMoney() {
    
    
        return money;
    }

    public void setMoney(int money) {
    
    
        this.money = money;
    }

    // 转账
    public void transfer(Account target, int amount) {
    
    
        synchronized(Account.class) {
    
    
            if (this.money >= amount) {
    
    
                this.setMoney(this.getMoney() - amount);
                target.setMoney(target.getMoney() + amount);
            }
        }
    }
}

6. Überwachen

Der Java-Objekt-Header
enthält Markword, das hauptsächlich Hashcode, Alter (GC-Lebenswert), ob Bias_lock eine voreingenommene Sperre ist, 01-Sperre
und Klassword speichert, das hauptsächlich auf Klassenobjekte (Klasseninformationen) verweist.
Wenn es sich um ein Array handelt, ist auch die Länge des Arrays enthalten. Die
Fügen Sie hier eine Bildbeschreibung ein
Monitorsperre
wird vom Betriebssystem bereitgestellt und die Kosten sind sehr hoch.
Das Arbeitsprinzip
besteht tatsächlich darin, die Adresse des Monitors in den 30 Bits davor aufzuzeichnen Markieren Sie obj und zeigen Sie auf den Monitor. Wenn ein Thread dann den Code des kritischen Abschnitts ausführen möchte, verweisen Sie den Besitzer des Monitors auf den entsprechenden Thread. Wenn ein anderer Thread eintrifft, prüfen Sie, ob obj mit einer Sperre verknüpft ist, und prüfen Sie dann, ob es einen Eigentümer gibt. Wenn ja, geben Sie die zu blockierende EntryList ein und warten Sie. Nachdem Sie darauf gewartet haben, dass der Thread die Sperre aufhebt, aktivieren Sie die Eintragsliste und starten Sie den Wettbewerb erneut.
Fügen Sie hier eine Bildbeschreibung ein
Der Winkel des Bytecodes
Der Winkel des Bytecodes besteht eigentlich darin, zuerst die Referenz der Sperre in Steckplatz 1 zu kopieren und dann den Monitoreintrag durchzuführen, die Markierung der Sperre auf den Monitor zu richten und den Hashcode usw. im Monitor zu speichern. Anschließend wird der Geschäftscode ausgeführt und schließlich der Referenzslot1 herausgenommen und anschließend der Monitorexit entsperrt. Darüber hinaus wird der Geschäftscode, also der Synchronisationsblock, auf Ausnahmen überwacht. Tritt eine Ausnahme auf, wird der Entsperrvorgang trotzdem durchgeführt.
Fügen Sie hier eine Bildbeschreibung ein

7. Optimierung synchronisieren

Kurzgeschichte:
Wenn es keine Konkurrenz zwischen zwei Threads gibt, können Sie ein leichtes Schloss verwenden, was dem Aufhängen einer Schultasche entspricht. Wenn sich herausstellt, dass es sich um die Schultasche der anderen Partei handelt, warten Sie draußen. Wenn der andere Thread später nicht mehr benötigt wird, kann der andere Thread den Namen auf der Außenseite der Tür eingravieren, was einem voreingenommenen Schloss entspricht. Wenn zu diesem Zeitpunkt jemand zum Wettbewerb kommt, wird er zu einer Schule aufgewertet Tasche, die ein leichtes Schloss ist. Später kam ein anderer Thread zurück und stellte fest, dass dieser Thread auf vielen Türen Namen eingraviert hatte, also ging er zum Betriebssystem, um diese Namen stapelweise in seine eigenen einzugravieren. Das heißt, die Vorspannungssperre zu ändern. Am Ende waren zu viele Namen eingraviert und die Präferenz wurde gestrichen.

Voreingenommene Sperren gelten ausschließlich für einen einzelnen Thread. Wenn ein einzelner Thread einen bestimmten Code ohne Konkurrenz verarbeitet, kann er eine voreingenommene Sperre verwenden. Wenn Konkurrenz vorhanden ist, kann er auf eine leichte Sperre aktualisiert werden.
1. Leichte Sperre
Das Wesentliche ist, dass der Sperrdatensatz des Stapelrahmens des Threads, der die temporäre Bereichsmethode aufruft, die Objektreferenz und die Objektmarkierungsinformationen speichert. Der nächste Schritt besteht darin, die dem Sperrdatensatz entsprechenden Sperrinformationen mit obj auszutauschen. Ändern Sie beispielsweise 01 in 00, um obj mitzuteilen, dass es sich um eine leichte Sperre handelt, und teilen Sie obj die Adresse des Sperrdatensatzes mit, was der Kennzeichnung von obj entspricht Wer sperrt das Etikett? Wenn es sich um eine Wiedereintrittssperre handelt, ist der Markword-Teil des Sperrdatensatzes null, was darauf hinweist, dass es sich um eine Wiedereintrittssperre handelt und dieselbe Sperre verwendet.
Fügen Sie hier eine Bildbeschreibung ein
2. Die Erweiterung der Sperre
erfolgt tatsächlich im Wettbewerb um leichte Sperren, und es gibt keinen Platz für konkurrierende Threads, um diese zu speichern. Zu diesem Zeitpunkt ist es notwendig, leichte Sperren in schwere Sperrenmonitore umzuwandeln. Tatsächlich geht es darum, die Marke zu markieren obj auf den Monitor übertragen. Dann zeigt der Besitzer des Monitors auf den Sperrdatensatz des aktuellen Threads. Legen Sie den blockierten Thread in die Warteschlange.
Während der Wiederherstellung versuchte CAS, den Sperrdatensatz des Threads wiederherzustellen, was jedoch fehlschlug. Zu diesem Zeitpunkt wird die Wiederherstellungsmethode in die Wiederherstellungsmethode für schwere Sperren geändert, die Liste wird aktiviert und dann wird der Eigentümer auf Null gesetzt, und die Threads konkurrieren erneut um den Monitor. Wenn nicht, stellen Sie die vom Monitor gespeicherten Hashcode-Informationen wieder her.
Fügen Sie hier eine Bildbeschreibung ein
3. Spin-Optimierung
ist eigentlich gleichbedeutend mit dem Warten auf die Ampel. Wenn die Ampel bald grün wird, warten Sie eine Weile. Wenn es lange dauert, dann ziehen Sie die Handbremse an. Drehen bedeutet, sich eine Weile zu drehen und darauf zu warten, dass andere die Schwergewichtssperre freigeben. Wenn dies einmal gelingt, erhöht sich die Erfolgswahrscheinlichkeit beim nächsten Mal und die Drehung wird noch einige Male durchgeführt. Wenn es nicht wartet, blockieren Sie.
Gründe für den Spin: Das Blockieren führt dazu, dass der Thread-Kontextwechsel CPU-Zeit und Ressourcen verbraucht. Die Geschwindigkeit ist relativ langsam.

4. Voreingenommene Sperren
Der Grund, warum voreingenommene Sperren verwendet werden, liegt darin, dass der Wiedereintritt der Sperre bei leichten Sperren jedes Mal CAS zum Vergleich aufruft. CAS ist eine Betriebssystembefehlsoperation, daher ist die Geschwindigkeit sehr langsam. Daher besteht die voreingenommene Sperre darin, ThreadId direkt dem Markierungswort zuzuweisen, sodass das Markierungswort beim nächsten Mal direkt in Java verglichen werden kann.
Voreingenommene Sperren werden verzögert und Objekte werden normalerweise nach einer Weile erstellt. Generieren Sie
zuerst voreingenommene Sperren – „leichte Sperren“ – „schwere Sperren“
. Wenn voreingenommene Sperren für kritische Abschnitte verwendet werden, wird dem Markierungswort die ID des entsprechenden Ausführungsthreads zugewiesen.
Wenn der Hashcode der Sperre entsperrt ist, wird die voreingenommene Sperre verboten, da der Hashcode zu viele Bits belegt.
Lightweight zeichnet den Hashcode im Sperrdatensatz auf und Heavyweight zeichnet ihn auf dem Monitor auf
. Wenn zwei Threads dieselbe Voreingenommenheit verwenden Sperre, die Sperre wird blockiert. Werden Sie unvoreingenommen und aktualisieren Sie auf leichte Sperren. Beim Batch-
Fügen Sie hier eine Bildbeschreibung ein
Rebiasing
handelt es sich tatsächlich um mehrere nicht konkurrierende Threads, die dieselbe Sperre verwenden. Wenn JVM feststellt, dass die Anzahl der aufgehobenen Sperrvoreingenommenheiten das 20-fache übersteigt, wird automatisch auf einen anderen Thread voreingenommen. Beispielsweise verwendet der T1-Thread eine Reihe von Sperren, und die Sperren sind auf T1 ausgerichtet. Aber wenn t2 diese Sperren verwendet und die Sperre widerrufen muss, sie aber mehr als 20 Mal voreingenommen wird, dann wirken sich diese Sperren alle auf t2 aus

Batch-Rückgängigmachen:
Wenn das Rückgängigmachen mehr als 40 Mal erfolgt, macht der JVM die Voreingenommenheit aller Objekte rückgängig.

5. Beseitigung der Sperre:
Tatsächlich stellt JIT fest, dass im kritischen Abschnitt der Sperre keine gemeinsam genutzten Ressourcen vorhanden sind, und hebt die Sperre auf.
Fügen Sie hier eine Bildbeschreibung ein

8. Warten und benachrichtigen

Kurzgeschichte:
Xiaonan braucht Zigaretten zum Arbeiten, muss aber auch die Schleuse besetzen, um andere am Zutritt zu hindern. Dann ist das Öffnen eines WaitSets zu diesem Zeitpunkt gleichbedeutend damit, Xiaonan in die Lounge gehen zu lassen, um sich zu entspannen und die Sperre aufzuheben. Wenn der Rauch eintrifft, benachrichtigen Sie Xiaonan, dass er mit der Arbeit fortfahren kann.

Der Unterschied zwischen „Blockiert“ und „Warten“
besteht tatsächlich darin, dass das Warten die Sperre aufhebt, während „Blockiert“ bedeutet, dass keine Sperre vorhanden ist.
Nachdem das Warten benachrichtigt wurde, muss es noch in die Eintragsliste eingegeben werden, um auf
Fügen Sie hier eine Bildbeschreibung ein
die Regeln des Wartens und Benachrichtigens zu warten.
Wenn der Thread das aufruft Objekt warten und benachrichtigen, es muss diese Sperre verwenden, um Eigentümer des Monitors zu werden. wann. Auf diese Weise können Sie warten, bis die Sperre aufgehoben wird, und von anderen Personen benachrichtigt werden, die die Sperre erworben haben.

@Slf4j(topic = "c.TestWaitNotify")
public class TestWaitNotify {
    
    
    final static Object obj = new Object();

    public static void main(String[] args) {
    
    

        new Thread(() -> {
    
    
            synchronized (obj) {
    
    
                log.debug("执行....");
                try {
    
    
                    obj.wait(); // 让线程在obj上一直等待下去
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                log.debug("其它代码....");
            }
        },"t1").start();

        new Thread(() -> {
    
    
            synchronized (obj) {
    
    
                log.debug("执行....");
                try {
    
    
                    obj.wait(); // 让线程在obj上一直等待下去
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                log.debug("其它代码....");
            }
        },"t2").start();

        // 主线程两秒后执行
        sleep(0.5);
        log.debug("唤醒 obj 上其它线程");
        synchronized (obj) {
    
    
//            obj.notify(); // 唤醒obj上一个线程
            obj.notifyAll(); // 唤醒obj上所有等待线程
        }
    }
}

Die Methode wait () kann die Wartezeit begrenzen, wait (Parameter)

9. Schlafen und warten

Der Unterschied besteht darin, dass
Sleep: Thread-Aufruf und statische Methode die Sperre nicht aufhebt.
wait: alle obj, aber es muss mit synchronisiert verwendet werden, um die Sperre aufzuheben.
Erweiterung
. Normalerweise wird die Sperre mit final hinzugefügt, um zu verhindern, dass sie aktiviert wird geändert.

Bei richtiger Verwendung
benötigt Xiaonan Rauch, um zu arbeiten. Wenn Sie den Schlaf verwenden, ohne die Sperre aufzuheben, warten andere Personen, die auf die Arbeit warten müssen. Aber warten Sie, Sie können Xiaonan die Sperre aufheben lassen, andere Threads arbeiten lassen und Xiaonan aufwecken.
Es liegt ein Problem
vor. Warten andere Threads auf die Sperre? Wenn ja, wird dadurch der falsche Thread aktiviert?

@Slf4j(topic = "c.TestCorrectPosture")
public class TestCorrectPostureStep2 {
    
    
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;

    public static void main(String[] args) {
    
    
        new Thread(() -> {
    
    
            synchronized (room) {
    
    
                log.debug("有烟没?[{}]", hasCigarette);
                if (!hasCigarette) {
    
    
                    log.debug("没烟,先歇会!");
                    try {
    
    
                        room.wait(2000);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
                log.debug("有烟没?[{}]", hasCigarette);
                if (hasCigarette) {
    
    
                    log.debug("可以开始干活了");
                }
            }
        }, "小南").start();

        for (int i = 0; i < 5; i++) {
    
    
            new Thread(() -> {
    
    
                synchronized (room) {
    
    
                    log.debug("可以开始干活了");
                }
            }, "其它人").start();
        }

        sleep(1);
        new Thread(() -> {
    
    
            synchronized (room) {
    
    
                hasCigarette = true;
                log.debug("烟到了噢!");
                room.notify();
            }
        }, "送烟的").start();
    }

}

Lösung:
Sie können while verwenden, um mehrmals zu bestimmen, ob die Bedingung wahr ist, und notifyAll direkt verwenden, um alle Threads aufzuwecken. Nachdem der Thread aufgeweckt wurde, kann er zunächst beurteilen, ob die Bedingung wahr ist. Wenn sie wahr ist, führen Sie Folgendes aus. Wenn sie nicht wahr ist, warten Sie weiter.
Fügen Sie hier eine Bildbeschreibung ein

10. Parken und entparken

Der Unterschied zwischen Warten und Benachrichtigen
besteht darin, dass es nicht mit einem Monitor verwendet werden muss
und Threads genau aktivieren und blockieren kann
. Sie können zuerst entparken, aber nicht zuerst benachrichtigen. Aber nach dem Ausparken funktioniert das Parken nicht

Funktionsprinzip
①Parken: Gehen Sie zuerst zum Zähler, um festzustellen, ob er 0 ist. Wenn ja, lassen Sie den Thread in die Warteschlange eintreten und setzen Sie den Zähler erneut auf 0. ②Unparken: Wenn der Thread
blockiert, setzen Sie zuerst den Zähler auf 1 und aktivieren Sie ihn dann Thread hochfahren und fortfahren. Ausführen und Zähler auf 0 setzen.
③ Zuerst entparken und dann parken, dann entparken und Zähler auf 1 hinzufügen. Park stellt fest, dass der Zähler 1 ist. Wenn Sie glauben, dass Sie noch Energie haben, fahren Sie mit der Ausführung fort.

Elftens: Verstehen Sie den Thread-Status erneut

Fall 1: new->runnable
thread start
Fall 2: runnable->waiting
benachrichtigen und warten. Warten tritt in die Blockierung ein, benachrichtigen ermöglicht es ihnen, erneut um die Sperre zu konkurrieren und in Runnable einzutreten. Andere gelangen weiterhin in die blockierte
Situation 3.
Parken und entparken
Situation 4.
Der Thread, der ihn über t.join aufruft, tritt in das Warten ein und wartet darauf, dass t die Aufgabe abschließt oder unterbrochen werden.
Die Situationen 5–8
sind eigentlich Warten. , Join, Sleep plus Zeit, alle von runnable->blocked
Situation 9
Wenn die Synchronisierung die Sperre nicht erhalten kann, wird sie in die blockierte
Situation 10 eintreten
. Nachdem der gesamte Code ausgeführt wurde, wird dies der Fall sein beendet
Fügen Sie hier eine Bildbeschreibung ein

12. Mehrere Schlösser

Ein Raum zum Schlafen und Lernen. Wenn jedoch nur eine Sperre schläft, können Sie nicht lernen und die Parallelität ist sehr gering. Zu diesem Zeitpunkt können Sie dann die Granularität des Schlosses verfeinern und es in zwei Schlösser aufteilen, eines für das Arbeitszimmer und eines für das Schlafzimmer, sodass die beiden Funktionen gleichzeitig ausgeführt werden können.

Problem:
Wenn zu viele Sperren vorhanden sind und ein Thread mehrere Sperren benötigt, kommt es zu einem Deadlock.

public class TestMultiLock {
    
    
    public static void main(String[] args) {
    
    
        BigRoom bigRoom = new BigRoom();
        new Thread(() -> {
    
    
            bigRoom.study();
        },"小南").start();
        new Thread(() -> {
    
    
            bigRoom.sleep();
        },"小女").start();
    }
}

@Slf4j(topic = "c.BigRoom")
class BigRoom {
    
    

    private final Object studyRoom = new Object();

    private final Object bedRoom = new Object();

    public void sleep() {
    
    
        synchronized (this) {
    
    
            log.debug("sleeping 2 小时");
            Sleeper.sleep(2);
        }
    }

    public void study() {
    
    
        synchronized (this) {
    
    
            log.debug("study 1 小时");
            Sleeper.sleep(1);
        }
    }

}

Deadlock-Fall
t1 hat A, will aber B, t2 hat B, will aber A

@Slf4j(topic = "c.TestDeadLock")
public class TestDeadLock {
    
    
    public static void main(String[] args) {
    
    
        test1();
    }

    private static void test1() {
    
    
        Object A = new Object();
        Object B = new Object();
        Thread t1 = new Thread(() -> {
    
    
            synchronized (A) {
    
    
                log.debug("lock A");
                sleep(1);
                synchronized (B) {
    
    
                    log.debug("lock B");
                    log.debug("操作...");
                }
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
    
    
            synchronized (B) {
    
    
                log.debug("lock B");
                sleep(0.5);
                synchronized (A) {
    
    
                    log.debug("lock A");
                    log.debug("操作...");
                }
            }
        }, "t2");
        t1.start();
        t2.start();
    }
}

Deadlock-Überprüfungsmethode
1. jps sucht die Prozess-ID und die Jstack-ID, um die Programminformationen anzuzeigen.
2. Jconsole zeigt die Prozessinformationen direkt an

Livelock
bedeutet eigentlich, dass beide Threads die Entsperrbedingungen des anderen ändern, was zu keiner Freigabe der Sperre, aber auch zu keiner Blockierung führt. Deadlock bedeutet, dass die Sperre der anderen Partei nicht freigegeben wird, wodurch der Thread blockiert wird.
Lösung:
Sie können die Ausführungszeit von Threads ändern und sie die Ausführung verschachteln lassen, um die Entsperrungsbedingung schnell auszuführen.
Fügen Sie hier eine Bildbeschreibung ein
Das Hungerproblem
besteht tatsächlich darin, dass der Thread nicht in der Lage war, die Sperre zu erhalten (um sie zu konkurrieren), was zu keiner Ausführung führte.

13. ReentrantLock

Im Vergleich zu „sync“
kann es unterbrochen werden
. Sie können das Erfassungs-Timeout festlegen. Nach dem Timeout wird der Erwerb der Sperre automatisch aufgegeben.
Faire Sperre, um Hungerprobleme zu vermeiden.
Es gibt viele Bedingungsvariablen.

Reentrant
Solange derselbe Thread dieselbe Sperre erhält, kann er ein zweites Mal verwendet werden. (Kann ein zweites Mal verwendet werden, wenn es nicht entsperrt ist)

@Slf4j(topic = "c.test22")
public class MyTest22 {
    
    
    public static ReentrantLock lock=new ReentrantLock();
    public static void main(String[] args) {
    
    
        lock.lock();
        try{
    
    
            log.debug("开始进入m1");
            m1();
        }finally {
    
    
            lock.unlock();
        }
    }

    public static void m1(){
    
    
        lock.lock();
        try {
    
    
            log.debug("m1进入");
            m2();
        }finally {
    
    
            lock.unlock();
        }
    }

    public static void m2(){
    
    
        lock.lock();
        try{
    
    
            log.debug("m2进入");
        }finally {
    
    
            lock.unlock();
        }
    }
}

LockInterrupt kann unterbrochen werden.Diese
Methode kann von anderen Threads unterbrochen werden, die auf die Sperre warten. Wenn es sich um eine Sperre handelt, hat sie keine Wirkung, selbst wenn sie unterbrochen wird. Diese Unterbrechbarkeit kann das Auftreten von Deadlocks reduzieren.

@Slf4j(topic = "c.test23")
public class MyTest23 {
    
    
    public static ReentrantLock lock=new ReentrantLock();
    public static void main(String[] args) {
    
    
        Thread t1 = new Thread(() -> {
    
    
            log.debug("上锁");
            lock.lock();
//            try {
    
    
//                lock.lockInterruptibly();
//            } catch (InterruptedException e) {
    
    
//                e.printStackTrace();
//                log.debug("无法获取锁");
//            }
            try {
    
    
                log.debug("获取到锁");
            } finally {
    
    
                lock.unlock();
            }
        }, "t1");
        t1.start();

        lock.lock();
        Sleeper.sleep(1);
        log.debug("帮助t1解锁");
        t1.interrupt();


    }
}

Timeout:
Hier können Sie tryLock verwenden, um das Timeout für den Erwerb der Sperre festzulegen. Bei einer Zeitüberschreitung wird der Erwerb der Sperre automatisch aufgegeben. Anstatt es verschlossen zu halten

@Slf4j(topic = "c.test24")
public class MyTest24 {
    
    
    public static ReentrantLock lock=new ReentrantLock();

    public static void main(String[] args) {
    
    
        Thread t1 = new Thread(() -> {
    
    
            log.debug("尝试获得锁");

            try {
    
    
                if(!lock.tryLock(2, TimeUnit.SECONDS)){
    
    
                    log.debug("获取锁失败");
                    return ;
                }
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
                log.debug("获取锁失败1");
                return;
            }
            try{
    
    
                log.debug("获取锁成功");
            }finally {
    
    
                lock.unlock();
            }
        }, "t1");

        log.debug("main获取锁");
        lock.lock();
        t1.start();
        Sleeper.sleep(1);
        log.debug("main解锁");
        lock.unlock();
    }
}

Um das Problem des Philosophen zu lösen
,
können Sie tryLock von ReentrantLock verwenden. Wenn der Versuch fehlschlägt, führen Sie den folgenden Prozess aus, anstatt auf den Erhalt der Sperre zu warten. Wenn Sie die Sperre nicht erhalten können, wird der Thread nicht blockiert.

 @Override
    public void run() {
    
    
        while (true) {
    
    
            if(left.tryLock()){
    
    

                try{
    
    
                    if(right.tryLock()){
    
    
                        try{
    
    
                          eat();
                        }finally {
    
    
                            right.unlock();
                        }
                    }
                }finally {
    
    
                    left.unlock();
                }
            }
        }
    }

Die Bedingungsvariable
definiert
, dass die Synchronisierung eine Sperre haben kann, und gibt die Sperre durch Warten und Benachrichtigen frei, um in waitSet einzutreten. Für ReentrantLock entspricht dies dem Vorhandensein mehrerer Lounge-WaitSets. Nach dem Erstellen der Sperre können Sie mehrere Bedingungsvariablen (mehrere Räume) erstellen. Es kann davon ausgegangen werden, dass es in ReentrantLock mehrere Lounges gibt und das Betreten verschiedener Lounges über verschiedene kleine verarbeitet werden kann Schlösser. Tatsächlich ist die freigegebene Sperre jedoch immer noch ReentrantLock und wird dann zur Verwendung an andere übergeben. Verschiedene Räume können jedoch über Bedingungsvariablen gesteuert werden, sodass derselbe Raum, aber der Thread-Bereich, um Sperren konkurrieren kann.

Hier werden Bedingungsvariablen verwendet, aber der Thread-Raum ist unterschiedlich und die Funktionsweise ist dieselbe. Die Fäden der erwachten Räume sind nicht dieselben.

@Slf4j(topic = "c.Test24")
public class Test224 {
    
    
    static final Object room = new Object();
    static boolean hasCigarette = false;
    static boolean hasTakeout = false;
    static ReentrantLock ROOM = new ReentrantLock();
    // 等待烟的休息室
    static Condition waitCigaretteSet = ROOM.newCondition();
    // 等外卖的休息室
    static Condition waitTakeoutSet = ROOM.newCondition();

    public static void main(String[] args) {
    
    


        new Thread(() -> {
    
    
            ROOM.lock();
            try {
    
    
                log.debug("有烟没?[{}]", hasCigarette);
                while (!hasCigarette) {
    
    
                    log.debug("没烟,先歇会!");
                    try {
    
    
                        waitCigaretteSet.await();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
                log.debug("可以开始干活了");
            } finally {
    
    
                ROOM.unlock();
            }
        }, "小南").start();

        new Thread(() -> {
    
    
            ROOM.lock();
            try {
    
    
                log.debug("外卖送到没?[{}]", hasTakeout);
                while (!hasTakeout) {
    
    
                    log.debug("没外卖,先歇会!");
                    try {
    
    
                        waitTakeoutSet.await();
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
                log.debug("可以开始干活了");
            } finally {
    
    
                ROOM.unlock();
            }
        }, "小女").start();

        sleep(1);
        new Thread(() -> {
    
    
            ROOM.lock();
            try {
    
    
                hasTakeout = true;
                waitTakeoutSet.signal();
            } finally {
    
    
                ROOM.unlock();
            }
        }, "送外卖的").start();

        sleep(1);

        new Thread(() -> {
    
    
            ROOM.lock();
            try {
    
    
                hasCigarette = true;
                waitCigaretteSet.signal();
            } finally {
    
    
                ROOM.unlock();
            }
        }, "送烟的").start();
    }

}

Verwenden Sie eine Sperre, um die Ausführungsreihenfolge festzulegen.
Die
Idee ist, dass beide Threads diese Sperre verwenden müssen, aber der T1-Thread muss ausgeführt werden, nachdem der T2-Thread ausgeführt wurde. Dann ist die Beurteilungsbedingung ein boolescher Wert. Wenn t2 ausgeführt wird, ändern Sie ihn und wecke den Thread t1 auf. Wenn Thread t1 feststellt, dass t2 nicht ausgeführt wird, tritt „wait“ ein, um zu warten. Wenn er fälschlicherweise aktiviert wird, kann er eine Schleife durch while durchlaufen und erneut eintreten, um zu warten.

@Slf4j(topic = "c.25")
public class MyTest25 {
    
    
    static Object lock=new Object();
    static boolean t2Run=false;
    public static void main(String[] args) {
    
    
        Thread t1 = new Thread(() -> {
    
    
            synchronized (lock){
    
    


                try {
    
    
                    while(!t2Run){
    
    
                        lock.wait();
                    }
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
                log.debug("1");
            }
        }, "t1");


        Thread t2 = new Thread(() -> {
    
    
            synchronized (lock){
    
    
                log.debug("2");
                //唤醒线程1
                t2Run=true;
                lock.notify();
            }
        }, "t2");

        t1.start();
        t2.start();

    }
}

Die zweite Methode
besteht tatsächlich darin, die Parkmethode von LockSupport zu verwenden. Wenn t1 zuerst ausgeführt wird, wird es geparkt und in die Blockierung eintreten, und nachdem t2 ausgeführt wurde, wird unpark t1 aktivieren. Es spielt keine Rolle, ob t2 zuerst ausgeführt wird. Das Entparken des Threads hier ändert den Zähler auf 1. Wenn Park zuerst den Zähler überprüft und feststellt, dass er bei t1 1 ist, kann die Ausführung fortgesetzt werden.

@Slf4j(topic = "c.26")
public class MyTest26 {
    
    
    public static void main(String[] args) {
    
    
        Thread t1 = new Thread(() -> {
    
    
            LockSupport.park();
            log.debug("1");
        }, "t1");

        Thread t2 = new Thread(() -> {
    
    
            log.debug("2");
            LockSupport.unpark(t1);
        }, "t2");

        t1.start();
        t2.start();


    }
}

Die Idee, abc nacheinander zu drucken (alternative Ausführung).
Die synchronisierte Methode
beurteilt tatsächlich, ob das Flag dieses Threads erreicht ist. Wenn es 1 ist, wird t1 ausgeführt. Wenn es 2 ist, wird t2 ausgeführt. Nach der Ausführung müssen Sie andere Threads aufwecken, um zu überprüfen, ob es sich um Ihre eigene Bedingung handelt. Wenn nicht, fahren Sie mit der Eingabe der Wartebedingung fort. Wenn die Bedingung erfüllt ist, erwerben Sie die Sperre und setzen Sie die Ausführung fort.

public class MyTest27 {
    
    
    public static void main(String[] args) {
    
    
        WaitNotify1 waitNotify1 = new WaitNotify1(1, 5);
        Thread t1 = new Thread(() -> {
    
    
            waitNotify1.print("a",1,2);
        }, "t1");
        Thread t2 = new Thread(() -> {
    
    
            waitNotify1.print("b",2,3);
        }, "t2");
        Thread t3 = new Thread(() -> {
    
    
            waitNotify1.print("c",3,1);
        }, "t3");

        t1.start();
        t2.start();
        t3.start();
    }
}

class  WaitNotify1{
    
    

    public void print(String s,int waitFlag,int nextFlag){
    
    

        synchronized (this){
    
    
            try {
    
    
                for(int i=0;i<loopNum;i++){
    
    
                    //如果不是1那么就等待
                    while(this.flag!=waitFlag){
    
    
                        this.wait();
                    }
                    System.out.print(s);
                    this.flag=nextFlag;
                    this.notifyAll();
                }

            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }

    }


    private int flag;
    private int loopNum;

    public WaitNotify1(int flag,int loopNum) {
    
    
        this.flag = flag;
        this.loopNum = loopNum;
    }
}

Die Idee von ReentrantLock ist es, sequentielle Ausführungsprobleme zu lösen.
Die Idee der Ausführung besteht hier tatsächlich darin, mehr Bedingungsvariablen durch Sperren zu öffnen. Bedingungsvariablen steuern jede Toilette. Verwenden Sie Bedingungsvariablen, um Threads zu blockieren und sie dann freizugeben . A, b und c werden jeweils dreimal geöffnet, und nach der Ausführung einer Bedingung wird eine weitere Bedingung aufgerufen, um die andere Bedingung zu entsperren und die Ausführung fortzusetzen. Zuerst müssen Sie alle drei Threads a, b und c sperren und dann einen manuell entsperren, um die Schleifenausführung zu starten.

public class MyTest28 {
    
    
    public static void main(String[] args) {
    
    
        WaitLock waitLock = new WaitLock(5);
        Condition a = waitLock.newCondition();
        Condition b = waitLock.newCondition();
        Condition c = waitLock.newCondition();

        new Thread(()->{
    
    
             waitLock.print("a",a,b);
         },"t1").start();
        new Thread(()->{
    
    
            waitLock.print("b",b,c);
        },"t2").start();
        new Thread(()->{
    
    
            waitLock.print("c",c,a);
        },"t3").start();

        Sleeper.sleep(1);
        waitLock.lock();
        try{
    
    
            //唤醒a
            a.signal();
        }finally {
    
    
            waitLock.unlock();
        }
    }
}

@Slf4j(topic = "c.lock")
class WaitLock extends ReentrantLock{
    
    

    private int loopNum;

    public WaitLock(int loopNum) {
    
    
        this.loopNum = loopNum;
    }

    public void print(String str,Condition cur,Condition next){
    
    
        for(int i=0;i<loopNum;i++){
    
    
            lock();
            try{
    
    
                cur.await();
                System.out.print(str);
                next.signal();
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            } finally {
    
    
                unlock();
            }

        }
    }
}

LockSupport-Ausführungsidee
Tatsächlich können Sie an dieser Stelle direkt Park verwenden, um zuerst drei Threads zu blockieren und dann manuell einen Thread zu öffnen. Wenn Sie den blockierten Thread öffnen müssen, müssen Sie ihn entparken (t), dh Sie müssen die entsprechenden Thread-Parameter übergeben, um den entsprechenden Thread aufzuwecken. Nachdem t1 ausgeführt wurde, t2 aktiviert wurde und t3 aktiviert wurde, nachdem t2 ausgeführt wurde, muss die zu diesem Zeitpunkt aufgerufene Methode die entsprechenden Thread-Parameter übergeben. Darüber hinaus können Thread-Parameter gemeinsam genutzt und im Methodenbereich platziert werden. Wenn sie im Hauptthread platziert werden, sind sie nicht sichtbar.

@Slf4j(topic = "c.test29")
public class MyTest29 {
    
    
    static Thread t1;
    static Thread t2;
    static Thread t3;
    public static void main(String[] args) {
    
    
        WaitPark waitPark = new WaitPark(5);
        t1=new Thread(()->{
    
    
            waitPark.print("a",t2);
        },"t1");
        t2=new Thread(()->{
    
    
            waitPark.print("b",t3);
        },"t2");
        t3=new Thread(()->{
    
    
            waitPark.print("c",t1);
        },"t3");

        t1.start();
        t2.start();
        t3.start();
        Sleeper.sleep(1);
        LockSupport.unpark(t1);
    }
}
@Slf4j(topic = "c.lock")
class WaitPark{
    
    
    private int loopNum;

    public WaitPark(int loopNum) {
    
    
        this.loopNum = loopNum;
    }



    public void print(String str,Thread t){
    
    
        for(int i=0;i<loopNum;i++){
    
    
            LockSupport.park();
            log.debug(str);
            LockSupport.unpark(t);
        }
    }
}

Ich denke du magst

Origin blog.csdn.net/weixin_45841848/article/details/132614018
Empfohlen
Rangfolge