Heim  >  Artikel  >  Java  >  Detaillierte Erläuterung der Verwendung der Mutex-Sperre der ReentrantLock-Klasse in der Java-Multithread-Programmierung

Detaillierte Erläuterung der Verwendung der Mutex-Sperre der ReentrantLock-Klasse in der Java-Multithread-Programmierung

高洛峰
高洛峰Original
2017-01-05 15:54:141392Durchsuche

0. Über Mutex-Sperren

Die sogenannte Mutex-Sperre bezieht sich auf eine Sperre, die jeweils nur von einem Thread gehalten werden kann. Vor jdk1.5 verwendeten wir normalerweise den synchronisierten Mechanismus, um mehrere zu steuern Threads Zugriff auf gemeinsam genutzte Ressourcen. Lock bietet nun einen größeren Bereich von Sperrvorgängen als der synchronisierte Mechanismus. Der Hauptunterschied zwischen Lock und dem synchronisierten Mechanismus:
Der synchronisierte Mechanismus bietet Zugriff auf die implizite Monitorsperre, die jedem Objekt zugeordnet ist erzwingt, dass alle Sperren in einer Blockstruktur erscheinen. Wenn mehrere Sperren erworben werden, müssen sie in umgekehrter Reihenfolge freigegeben werden, solange der Thread, der Code ausführt, den Umfang des synchronisierten Anweisungsblocks überschreitet Wird die Sperre überschritten, muss der Lock-Mechanismus die Methode unlock() des Lock-Objekts explizit aufrufen. Dies bedeutet, dass das Erlangen der Sperre und das Freigeben der Sperre nicht in derselben Blockstruktur erfolgen Update Kostenlose Bestellung von Schlössern möglich.

1. Einführung in ReentrantLock
ReentrantLock ist eine wiedereintrittsfähige Mutex-Sperre, auch bekannt als „exklusive Sperre“.
Wie der Name schon sagt, kann die ReentrantLock-Sperre jeweils nur von einem Thread gehalten werden, und reentrant bedeutet, dass die ReentrantLock-Sperre mehrmals von einem einzelnen Thread erworben werden kann.
ReentrantLock ist in „faire Sperre“ und „unfaire Sperre“ unterteilt. Der Unterschied spiegelt sich darin wider, ob der Sperrenerfassungsmechanismus fair ist. „Sperre“ soll konkurrierende Ressourcen schützen und Fehler verhindern, die dadurch verursacht werden, dass mehrere Threads gleichzeitig Threads ausführen. ReentrantLock kann nur von einem Thread zum gleichen Zeitpunkt erworben werden (wenn ein Thread die „Sperre“ erhält, müssen andere Threads warten). ); ReentrantLock Alle Threads, die die Sperre erhalten, werden über eine FIFO-Warteschlange verwaltet. Beim „fair lock“-Mechanismus stehen Threads in der Warteschlange, um Sperren nacheinander zu erwerben. Bei „unfairer Sperre“ erhalten die Threads die Sperre, wenn sie zum Erwerb verfügbar ist, unabhängig davon, ob sie sich am Anfang der Warteschlange befinden.

ReentrantLock-Funktionsliste

// 创建一个 ReentrantLock ,默认是“非公平锁”。
ReentrantLock()
// 创建策略是fair的 ReentrantLock。fair为true表示是公平锁,fair为false表示是非公平锁。
ReentrantLock(boolean fair)
 
// 查询当前线程保持此锁的次数。
int getHoldCount()
// 返回目前拥有此锁的线程,如果此锁不被任何线程拥有,则返回 null。
protected Thread getOwner()
// 返回一个 collection,它包含可能正等待获取此锁的线程。
protected Collection<Thread> getQueuedThreads()
// 返回正等待获取此锁的线程估计数。
int getQueueLength()
// 返回一个 collection,它包含可能正在等待与此锁相关给定条件的那些线程。
protected Collection<Thread> getWaitingThreads(Condition condition)
// 返回等待与此锁相关的给定条件的线程估计数。
int getWaitQueueLength(Condition condition)
// 查询给定线程是否正在等待获取此锁。
boolean hasQueuedThread(Thread thread)
// 查询是否有些线程正在等待获取此锁。
boolean hasQueuedThreads()
// 查询是否有些线程正在等待与此锁有关的给定条件。
boolean hasWaiters(Condition condition)
// 如果是“公平锁”返回true,否则返回false。
boolean isFair()
// 查询当前线程是否保持此锁。
boolean isHeldByCurrentThread()
// 查询此锁是否由任意线程保持。
boolean isLocked()
// 获取锁。
void lock()
// 如果当前线程未被中断,则获取锁。
void lockInterruptibly()
// 返回用来与此 Lock 实例一起使用的 Condition 实例。
Condition newCondition()
// 仅在调用时锁未被另一个线程保持的情况下,才获取该锁。
boolean tryLock()
// 如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁。
boolean tryLock(long timeout, TimeUnit unit)
// 试图释放此锁。
void unlock()

2. ReentrantLock-Beispiel
Durch den Vergleich von „Beispiel 1“ und „Beispiel 2“ können wir die Funktionen des Sperrens und Entsperrens klar verstehen
2.1 Beispiel 1

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
// LockTest1.java
// 仓库
class Depot { 
 private int size;  // 仓库的实际数量
 private Lock lock;  // 独占锁
 
 public Depot() {
  this.size = 0;
  this.lock = new ReentrantLock();
 }
 
 public void produce(int val) {
  lock.lock();
  try {
   size += val;
   System.out.printf("%s produce(%d) --> size=%d\n", 
     Thread.currentThread().getName(), val, size);
  } finally {
   lock.unlock();
  }
 }
 
 public void consume(int val) {
  lock.lock();
  try {
   size -= val;
   System.out.printf("%s consume(%d) <-- size=%d\n", 
     Thread.currentThread().getName(), val, size);
  } finally {
   lock.unlock();
  }
 }
}; 
 
// 生产者
class Producer {
 private Depot depot;
 
 public Producer(Depot depot) {
  this.depot = depot;
 }
 
 // 消费产品:新建一个线程向仓库中生产产品。
 public void produce(final int val) {
  new Thread() {
   public void run() {
    depot.produce(val);
   }
  }.start();
 }
}
 
// 消费者
class Customer {
 private Depot depot;
 
 public Customer(Depot depot) {
  this.depot = depot;
 }
 
 // 消费产品:新建一个线程从仓库中消费产品。
 public void consume(final int val) {
  new Thread() {
   public void run() {
    depot.consume(val);
   }
  }.start();
 }
}
 
public class LockTest1 { 
 public static void main(String[] args) { 
  Depot mDepot = new Depot();
  Producer mPro = new Producer(mDepot);
  Customer mCus = new Customer(mDepot);
 
  mPro.produce(60);
  mPro.produce(120);
  mCus.consume(90);
  mCus.consume(150);
  mPro.produce(110);
 }
}

Laufendes Ergebnis:

Thread-0 produce(60) --> size=60
Thread-1 produce(120) --> size=180
Thread-3 consume(150) <-- size=30
Thread-2 consume(90) <-- size=-60
Thread-4 produce(110) --> size=50

Ergebnisanalyse:
(1) Depot ist ein Lager. Mit „produzieren“ können Waren im Lager produziert werden, und mit „verbrauchen“ können Waren im Lager konsumiert werden. Der sich gegenseitig ausschließende Zugriff auf das Lager wird durch eine exklusive Sperre erreicht: Vor dem Betrieb (Produktion/Verbrauch) von Waren im Lager wird das Lager durch lock() gesperrt und nach dem Vorgang durch unlock() entsperrt.
(2) Produzent ist die Produzentenklasse. Rufen Sie die Funktion Produce() in Producer auf, um einen neuen Thread für die Produktion von Produkten im Lager zu erstellen.
(3) Kunde ist die Verbraucherklasse. Rufen Sie die Funktion „consume()“ in „Customer“ auf, um einen neuen Thread zum Konsumieren von Produkten im Lager zu erstellen.
(4) Im Hauptthread main werden wir einen neuen Producer mPro und einen neuen Consumer mCus erstellen. Sie produzieren bzw. verbrauchen Produkte im Lager.
Entsprechend der Hauptproduktions-/Verbrauchsmenge sollten die letzten verbleibenden Produkte im Lager 50 sein. Die Laufergebnisse entsprechen unseren Erwartungen!
Bei diesem Modell gibt es zwei Probleme:
(1) In Wirklichkeit kann die Kapazität des Lagers keine negative Zahl sein. Allerdings kann die Lagerkapazität in diesem Modell negativ sein, was der Realität widerspricht!
(2) In Wirklichkeit ist die Kapazität des Lagers begrenzt. Der Kapazität sind bei diesem Modell jedoch wirklich keine Grenzen gesetzt!
Wir werden gleich darüber sprechen, wie diese beiden Probleme gelöst werden können. Schauen wir uns nun ein einfaches Beispiel 2 an. Durch den Vergleich von „Beispiel 1“ und „Beispiel 2“ können wir die Verwendung von lock() und unlock() besser verstehen.

2.2 Beispiel 2

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
// LockTest2.java
// 仓库
class Depot {
  private int size;    // 仓库的实际数量
  private Lock lock;    // 独占锁
 
  public Depot() {
    this.size = 0;
    this.lock = new ReentrantLock();
  }
 
  public void produce(int val) {
//    lock.lock();
//    try {
      size += val;
      System.out.printf("%s produce(%d) --> size=%d\n",
          Thread.currentThread().getName(), val, size);
//    } catch (InterruptedException e) {
//    } finally {
//      lock.unlock();
//    }
  }
 
  public void consume(int val) {
//    lock.lock();
//    try {
      size -= val;
      System.out.printf("%s consume(%d) <-- size=%d\n",
          Thread.currentThread().getName(), val, size);
//    } finally {
//      lock.unlock();
//    }
  }
};
 
// 生产者
class Producer {
  private Depot depot;
 
  public Producer(Depot depot) {
    this.depot = depot;
  }
 
  // 消费产品:新建一个线程向仓库中生产产品。
  public void produce(final int val) {
    new Thread() {
      public void run() {
        depot.produce(val);
      }
    }.start();
  }
}
 
// 消费者
class Customer {
  private Depot depot;
 
  public Customer(Depot depot) {
    this.depot = depot;
  }
 
  // 消费产品:新建一个线程从仓库中消费产品。
  public void consume(final int val) {
    new Thread() {
      public void run() {
        depot.consume(val);
      }
    }.start();
  }
}
 
public class LockTest2 {
  public static void main(String[] args) {
    Depot mDepot = new Depot();
    Producer mPro = new Producer(mDepot);
    Customer mCus = new Customer(mDepot);
 
    mPro.produce(60);
    mPro.produce(120);
    mCus.consume(90);
    mCus.consume(150);
    mPro.produce(110);
  }
}

(eine bestimmte Zeit) Laufergebnisse:

Thread-0 produce(60) --> size=-60
Thread-4 produce(110) --> size=50
Thread-2 consume(90) <-- size=-60
Thread-1 produce(120) --> size=-60
Thread-3 consume(150) <-- size=-60

Ergebnisbeschreibung:
„Beispiel 2“ entfernt die Sperre basierend auf „Beispiel 1“. In „Beispiel 2“ ist das letzte verbleibende Produkt im Lager -60, nicht die 50, die wir erwartet hatten. Der Grund dafür ist, dass wir keinen sich gegenseitig ausschließenden Zugriff auf das Lager implementiert haben.

2.3 Beispiel 3
In „Beispiel 3“ verwenden wir Bedingung, um die beiden Probleme in „Beispiel 1“ zu lösen: „Die Kapazität des Lagers darf keine negative Zahl sein“ und „Die Kapazität des Lager ist eingeschränkt“.
Die Lösung für dieses Problem liegt in der Bedingung. Condition muss in Verbindung mit Lock verwendet werden: Über die Methode „await()“ in Condition kann der Thread blockiert werden [ähnlich wie „wait()“], über die Methode „signal()“ von Condition kann der Thread geweckt werden [ähnlich wie „notify“. ()].

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.Condition;
 
// LockTest3.java
// 仓库
class Depot {
  private int capacity;  // 仓库的容量
  private int size;    // 仓库的实际数量
  private Lock lock;    // 独占锁
  private Condition fullCondtion;      // 生产条件
  private Condition emptyCondtion;    // 消费条件
 
  public Depot(int capacity) {
    this.capacity = capacity;
    this.size = 0;
    this.lock = new ReentrantLock();
    this.fullCondtion = lock.newCondition();
    this.emptyCondtion = lock.newCondition();
  }
 
  public void produce(int val) {
    lock.lock();
    try {
       // left 表示“想要生产的数量”(有可能生产量太多,需多此生产)
      int left = val;
      while (left > 0) {
        // 库存已满时,等待“消费者”消费产品。
        while (size >= capacity)
          fullCondtion.await();
        // 获取“实际生产的数量”(即库存中新增的数量)
        // 如果“库存”+“想要生产的数量”>“总的容量”,则“实际增量”=“总的容量”-“当前容量”。(此时填满仓库)
        // 否则“实际增量”=“想要生产的数量”
        int inc = (size+left)>capacity ? (capacity-size) : left;
        size += inc;
        left -= inc;
        System.out.printf("%s produce(%3d) --> left=%3d, inc=%3d, size=%3d\n",
            Thread.currentThread().getName(), val, left, inc, size);
        // 通知“消费者”可以消费了。
        emptyCondtion.signal();
      }
    } catch (InterruptedException e) {
    } finally {
      lock.unlock();
    }
  }
 
  public void consume(int val) {
    lock.lock();
    try {
      // left 表示“客户要消费数量”(有可能消费量太大,库存不够,需多此消费)
      int left = val;
      while (left > 0) {
        // 库存为0时,等待“生产者”生产产品。
        while (size <= 0)
          emptyCondtion.await();
        // 获取“实际消费的数量”(即库存中实际减少的数量)
        // 如果“库存”<“客户要消费的数量”,则“实际消费量”=“库存”;
        // 否则,“实际消费量”=“客户要消费的数量”。
        int dec = (size<left) ? size : left;
        size -= dec;
        left -= dec;
        System.out.printf("%s consume(%3d) <-- left=%3d, dec=%3d, size=%3d\n",
            Thread.currentThread().getName(), val, left, dec, size);
        fullCondtion.signal();
      }
    } catch (InterruptedException e) {
    } finally {
      lock.unlock();
    }
  }
 
  public String toString() {
    return "capacity:"+capacity+", actual size:"+size;
  }
};
 
// 生产者
class Producer {
  private Depot depot;
 
  public Producer(Depot depot) {
    this.depot = depot;
  }
 
  // 消费产品:新建一个线程向仓库中生产产品。
  public void produce(final int val) {
    new Thread() {
      public void run() {
        depot.produce(val);
      }
    }.start();
  }
}
 
// 消费者
class Customer {
  private Depot depot;
 
  public Customer(Depot depot) {
    this.depot = depot;
  }
 
  // 消费产品:新建一个线程从仓库中消费产品。
  public void consume(final int val) {
    new Thread() {
      public void run() {
        depot.consume(val);
      }
    }.start();
  }
}
 
public class LockTest3 {
  public static void main(String[] args) {
    Depot mDepot = new Depot(100);
    Producer mPro = new Producer(mDepot);
    Customer mCus = new Customer(mDepot);
 
    mPro.produce(60);
    mPro.produce(120);
    mCus.consume(90);
    mCus.consume(150);
    mPro.produce(110);
  }
}

(Eine bestimmte Zeit) Laufergebnis:

Thread-0 produce( 60) --> left= 0, inc= 60, size= 60
Thread-1 produce(120) --> left= 80, inc= 40, size=100
Thread-2 consume( 90) <-- left= 0, dec= 90, size= 10
Thread-3 consume(150) <-- left=140, dec= 10, size= 0
Thread-4 produce(110) --> left= 10, inc=100, size=100
Thread-3 consume(150) <-- left= 40, dec=100, size= 0
Thread-4 produce(110) --> left= 0, inc= 10, size= 10
Thread-3 consume(150) <-- left= 30, dec= 10, size= 0
Thread-1 produce(120) --> left= 0, inc= 80, size= 80
Thread-3 consume(150) <-- left= 0, dec= 30, size= 50

Ausführlichere Erklärung von Java Multi -Thread-Programmierung Weitere Artikel zur Verwendung der ReentrantLock-Klasse finden Sie auf der chinesischen PHP-Website!


Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn