Heim  >  Artikel  >  Java  >  So verwenden Sie Lock in Java-Multithreading

So verwenden Sie Lock in Java-Multithreading

PHPz
PHPznach vorne
2023-05-12 14:46:061523Durchsuche

Nach Jdk1.5 gibt es unter dem Paket java.util.concurrent.locks eine Reihe von Schnittstellen und Klassen, die die Thread-Synchronisierung implementieren. Wenn es um die Thread-Synchronisierung geht, denkt jeder vielleicht an das synchronisierte Schlüsselwort

Dies ist ein Integriertes Schlüsselwort in Java, das für die Thread-Synchronisierung verwendet wird. Dieses Schlüsselwort weist jedoch viele Mängel auf und ist nicht sehr praktisch und intuitiv zu verwenden. Daher wird Lock angezeigt. Im Folgenden werden wir Lock vergleichen und erklären.

Normalerweise stoßen wir bei der Verwendung des synchronisierten Schlüsselworts auf die folgenden Probleme:

(1) Unkontrollierbarkeit, es ist nicht möglich, Sperren nach Belieben zu sperren und freizugeben.

(2) Die Effizienz ist relativ gering. Wenn wir beispielsweise zwei Dateien gleichzeitig lesen, haben wir keinen Einfluss darauf, ob das Leseobjekt synchronisiert ist Solange es einen gibt Sobald der Thread eintritt, müssen andere Threads warten.

(3) Es gibt keine Möglichkeit herauszufinden, ob der Thread die Sperre erhalten hat.

Sperre kann die oben genannten synchronisierten Probleme sehr gut lösen, und nach JDK1.5 werden auch verschiedene Sperren bereitgestellt, z. B. Lese-/Schreibsperren. Bei der Verwendung synchronisierter Schlüssel ist jedoch Folgendes zu beachten Um die Sperre manuell aufzuheben, müssen Sie die Sperre manuell aufheben, um die Sperre zu verwenden. Erfahren Sie mehr über Lock-Sperren.

Lock ist eine Schnittstelle der oberen Ebene. Ihr Prototyp sieht wie folgt aus und bietet insgesamt 6 Methoden:

public interface Lock {
  // 用来获取锁,如果锁已经被其他线程获取,则一直等待,直到获取到锁
   void lock();
  // 该方法获取锁时,可以响应中断,比如现在有两个线程,一个已经获取到了锁,另一个线程调用这个方法正在等待锁,但是此刻又不想让这个线程一直在这死等,可以通过
    调用线程的Thread.interrupted()方法,来中断线程的等待过程
  void lockInterruptibly() throws InterruptedException;
  // tryLock方法会返回bool值,该方法会尝试着获取锁,如果获取到锁,就返回true,如果没有获取到锁,就返回false,但是该方法会立刻返回,而不会一直等待
   boolean tryLock();
  // 这个方法和上面的tryLock差不多是一样的,只是会尝试指定的时间,如果在指定的时间内拿到了锁,则会返回true,如果在指定的时间内没有拿到锁,则会返回false
   boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
  // 释放锁
   void unlock();
  // 实现线程通信,相当于wait和notify,后面会单独讲解
   Condition newCondition();
}

Wie verwendet man diese Methoden? Wie bereits erwähnt, erfordert die Verwendung von Lock eine manuelle Freigabe der Sperre. Wenn jedoch eine Ausnahme im Programm ausgelöst wird, kann die Sperre nicht aufgehoben werden, was zu einem Deadlock führen kann. Wenn wir Lock verwenden, gibt es ein festes Format Folgendes:

Lock l = ...;
      l.lock();
      try {
        // access the resource protected by this lock
      } finally {// 必须使用try,最后在finally里面释放锁
        l.unlock();
      }

Schauen wir uns ein einfaches Beispiel an. Der Code lautet wie folgt:

/**
 * 描述:Lock使用
 */
public class LockDemo {
    // new一个锁对象,注意此处必须声明成类对象,保持只有一把锁,ReentrantLock是Lock的唯一实现类
   Lock lock = new ReentrantLock();
   public void readFile(String fileMessage){
      lock.lock();// 上锁
      try{
         System.out.println(Thread.currentThread().getName()+"得到了锁,正在读取文件……");
         for(int i=0; i<fileMessage.length(); i++){
            System.out.print(fileMessage.charAt(i));
         }
         System.out.println();
         System.out.println("文件读取完毕!");
      }finally{
         System.out.println(Thread.currentThread().getName()+"释放了锁!");
         lock.unlock();
      }
   }
   public void demo(final String fileMessage){
      // 创建若干个线程
      ExecutorService service = Executors.newCachedThreadPool();
      // 提交20个任务
      for(int i=0; i<20; i++){
         service.execute(new Runnable() {
            @Override
            public void run() {
               readFile(fileMessage);
               try {
                  Thread.sleep(20);
               } catch (InterruptedException e) {
                  e.printStackTrace();
               }
            }
         });
      }
    // 释放线程池中的线程
      service.shutdown();
   }
}

Vergleich von Lock und synchronisiert

1. Funktion

lock und synchronisiert werden beide in Java verwendet, um Thread-Sicherheit A zu lösen Werkzeug für das Problem.

2. Quelle

sychronized ist ein Schlüsselwort in Java.

lock ist eine im JUC-Paket bereitgestellte Schnittstelle. Diese Schnittstelle verfügt über viele Implementierungsklassen, einschließlich unseres am häufigsten verwendeten ReentrantLock (Wiedereintrittssperre).

3. Sperrstärke

sychronized kann die Sperrstärke auf zwei Arten steuern:

Ändern Sie das synchronisierte Schlüsselwort auf Methodenebene.

Dekoration auf Codeblöcken.

Unterschiede bei Sperrobjekten: Wenn das Sperrobjekt ein statisches Objekt oder ein Klassenobjekt ist, dann ist diese Sperre eine globale Sperre.

Das Sperrobjekt ist ein normales Instanzobjekt und der Umfang dieser Sperre hängt vom Lebenszyklus dieser Instanz ab.

Die Stärke der Sperre wird durch die beiden Methoden lock() und unlock() bestimmt. Der Code zwischen den beiden Methoden ist garantiert threadsicher. Der Umfang der Sperre hängt vom Lebenszyklus der Sperrinstanz ab.


4. Flexibilität

Lock ist flexibler als synchronisiert.

Lock kann unabhängig entscheiden, wann die Sperre gesperrt und freigegeben werden soll. Rufen Sie einfach die Methoden lock() und unlock() von lock auf.

sychronisiert Da es sich um ein Schlüsselwort handelt, kann die nicht blockierende Konkurrenzsperrmethode nicht implementiert werden. Nachdem ein Thread die Sperre erhalten hat, können andere Sperren nur darauf warten, dass dieser Thread freigegeben wird, bevor sie die Chance haben, die Sperre zu erhalten.

5. Faire Sperre und unfaire Sperre: Mehrere Threads erhalten Sperren in der Reihenfolge, in der sie Sperren beantragen. Die Threads werden direkt in die Warteschlange aufgenommen und sind immer die Ersten in der Warteschlange, um die Sperre zu erhalten.

Vorteile: Alle Threads können Ressourcen erhalten und verhungern nicht.

Nachteile: Geringer Durchsatz, mit Ausnahme des ersten Threads in der Warteschlange werden alle anderen Threads blockiert und die Kosten für das Aufwecken blockierter Threads durch die CPU sind hoch.

Unfaire Sperre: Wenn mehrere Threads die Sperre erhalten möchten, werden sie direkt versuchen, sie zu erhalten. Wenn sie sie nicht erhalten können, werden sie in die Warteschlange gestellt. Wenn sie sie erhalten können, werden sie die Sperre direkt erwerben.

Vorteile: Es kann den CPU-Overhead beim Aufwecken von Threads reduzieren, die Gesamtdurchsatzeffizienz wird höher sein und die CPU muss nicht alle Threads aufwecken, wodurch die Anzahl der aufgeweckten Threads verringert wird.

Nachteil: Der Thread in der Mitte der Warteschlange kann die Sperre möglicherweise nicht oder für längere Zeit nicht erhalten und verhungert schließlich. lock bietet zwei Mechanismen: Fair Lock und Unfair Lock (Standard-Unfair Lock).

synchronisiert ist eine unfaire Sperre.

6. Ob die Sperre aufgrund einer Ausnahme aufgehoben werden soll

Die Freigabe der synchronisierten Sperre ist passiv und wird nur freigegeben, wenn die Ausführung des synchronisierten synchronisierten Codeblocks endet oder eine Ausnahme auftritt.


Wenn in der Sperre eine Ausnahme auftritt, wird die belegte Sperre nicht aktiv freigegeben und muss manuell mit unlock() freigegeben werden. Daher setzen wir normalerweise den Synchronisationscodeblock in try-catch und schreiben die Methode unlock() hinein Endlich Deadlocks vermeiden.

7. Stellen Sie fest, ob die Sperre erworben werden kann

synchronisiert nicht.

lock bietet eine nicht blockierende Wettbewerbssperrmethode trylock (), und der Rückgabewert ist vom Typ Boolean. Es zeigt an, dass damit versucht wird, die Sperre zu erhalten: Sie gibt „true“ zurück, wenn die Erfassung erfolgreich ist; sie gibt „false“ zurück, wenn die Erfassung fehlschlägt. Diese Methode wird in jedem Fall sofort zurückgegeben.

8. Die Planungsmethode

synchronized verwendet die Wait-, Notify- und NotifyAll-Methoden des Objektobjekts selbst, während Lock Condition zum Planen zwischen Threads verwendet.

9. Kann es unterbrochen werden

synchronized kann nur auf die Aufhebung der Sperre warten und nicht auf Interrupts reagieren.

Sie können interrupt() verwenden, um zu unterbrechen, während Sie auf die Sperre warten.

10. Leistung

Wenn die Konkurrenz nicht groß ist, ist die Leistung ungefähr gleich; wenn die Konkurrenz groß ist, ist die Leistung von Lock besser.

Lock kann auch readwritelock verwenden, um Lesen und Schreiben zu trennen und so die Effizienz von Multithread-Lesevorgängen zu verbessern.

11. Synchronisiertes Sperren-Upgrade

Der synchronisierte Codeblock wird durch ein Paar Monitorenter/Monitorexit-Anweisungen implementiert. Die Implementierung von Monitor basiert vollständig auf der Mutex-Sperre im Betriebssystem, da der Wechsel vom Benutzermodus in den Kernelmodus erforderlich ist. Der Synchronisierungsvorgang ist ein undifferenzierter, schwerer Vorgang.

Jetzt bietet die JVM also drei verschiedene Sperren: voreingenommene Sperren, leichte Sperren und schwere Sperren.

Voreingenommene Sperre:
Wenn kein Wettbewerb stattfindet, wird standardmäßig eine voreingenommene Sperre verwendet. Der Thread verwendet die CAS-Operation, um die Thread-ID im Objektheader festzulegen, um anzuzeigen, dass das Objekt auf den aktuellen Thread ausgerichtet ist.

Zweck: In vielen Anwendungsszenarien wird der Lebenszyklus der meisten Objekte von höchstens einem Thread gesperrt, wenn keine Konkurrenz vorhanden ist.

Leichte Sperre:
Die JVM vergleicht die Thread-ID des aktuellen Threads mit der Thread-ID im Java-Objektheader, um festzustellen, ob sie konsistent ist (z. B. möchte Thread 2 um das Sperrobjekt konkurrieren). Sie müssen überprüfen, ob der im Java-Objekt-Header aufgezeichnete Thread 1 aktiv ist (die voreingenommene Sperre wird nicht aktiv freigegeben, es handelt sich also immer noch um die gespeicherte Thread-ID von Thread 1). Wenn sie nicht überlebt, ist das Sperrobjekt immer noch voreingenommen Sperre (die Thread-ID im Objektheader ist die von Thread 2); wenn sie bestehen bleibt, wird die voreingenommene Sperre aufgehoben und zu einer leichten Sperre hochgestuft.

Wenn andere Threads mit leichten Sperren auf Ressourcen zugreifen möchten, wird die Spin-Lock-Optimierung verwendet, um auf die Ressourcen zuzugreifen.

Zweck: Es gibt nicht viele Threads, die um das Sperrobjekt konkurrieren, und die Threads halten die Sperre nicht lange aufrecht. Da das Blockieren des Threads erfordert, dass die CPU vom Benutzermodus in den Kernelmodus wechselt, was teuer ist. Wenn die Sperre kurz nach dem Blockieren aufgehoben wird, überwiegt der Gewinn den Verlust. Daher ist es besser, den Thread zu diesem Zeitpunkt nicht zu blockieren Lassen Sie es drehen und warten Sie, bis die Sperre aufgehoben wird.

Schwergewichtige Sperre:
Wenn die Drehung fehlschlägt, besteht eine hohe Wahrscheinlichkeit, dass die Selbstauswahl erneut fehlschlägt. Daher wird sie direkt auf eine Schwergewichtssperre aktualisiert, um Threads zu blockieren und den CPU-Verbrauch zu reduzieren.

Wenn die Sperre auf eine Schwergewichtssperre aktualisiert wird, werden Threads, die die Sperre nicht ergriffen haben, blockiert und in die Blockierungswarteschlange eingegeben.

Das obige ist der detaillierte Inhalt vonSo verwenden Sie Lock in Java-Multithreading. 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