Detaillierte Code-Erklärung der Java-exklusiven Sperrimplementierung
In diesem Artikel werden hauptsächlich die relevanten Inhalte der Java-Programmierung zum Implementieren einer exklusiven Sperre vorgestellt, die zum Implementieren dieser Codesperre erforderlichen Funktionen und die Lösung des Autors beschrieben und anschließend der Entwurfsquellcode für alle freigegeben, die ihn benötigen .
1. Vorwort
An einem bestimmten Tag eines bestimmten Monats und Jahres sagte ein Kollege, dass es eine Datei-Exklusiv-Sperrfunktion gibt Die Anforderungen lauten wie folgt:
(1) Schreiboperationen sind exklusive Eigenschaften
(2) Anwendbar auf Multithreads desselben Prozesses/auch anwendbar auf exklusive Operationen mehrerer Prozesse
(3) Fehlertoleranz: Sperrenerfassung Wenn der Prozess abstürzt, hat dies keinen Einfluss auf die normale Sperrenerfassung durch nachfolgende Prozesse
2. Lösung
1. Die Idee von
Im Java-Bereich ist die ausschließliche Implementierung von Multithreading im selben Prozess relativ einfach . Sie können beispielsweise eine Thread-Synchronisierungsvariable verwenden, um anzugeben, ob sie gesperrt ist. Die ausschließliche Implementierung verschiedener Prozesse ist jedoch umständlicher. Unter Verwendung der vorhandenen API habe ich mir java.nio.channels.FileLock: natürlich wie folgt vorgestellt:
/** * @param file * @param strToWrite * @param append * @param lockTime 以毫秒为单位,该值只是方便模拟排他锁时使用,-1表示不考虑该字段 * @return */ public static boolean lockAndWrite(File file, String strToWrite, boolean append,int lockTime){ if(!file.exists()){ return false; } RandomAccessFile fis = null; FileChannel fileChannel = null; FileLock fl = null; long tsBegin = System.currentTimeMillis(); try { fis = new RandomAccessFile(file, "rw"); fileChannel = fis.getChannel(); fl = fileChannel.tryLock(); if(fl == null || !fl.isValid()){ return false; } log.info("threadId = {} lock success", Thread.currentThread()); // if append if(append){ long length = fis.length(); fis.seek(length); fis.writeUTF(strToWrite); //if not, clear the content , then write }else{ fis.setLength(0); fis.writeUTF(strToWrite); } long tsEnd = System.currentTimeMillis(); long totalCost = (tsEnd - tsBegin); if(totalCost < lockTime){ Thread.sleep(lockTime - totalCost); } } catch (Exception e) { log.error("RandomAccessFile error",e); return false; }finally{ if(fl != null){ try { fl.release(); } catch (IOException e) { e.printStackTrace(); } } if(fileChannel != null){ try { fileChannel.close(); } catch (IOException e) { e.printStackTrace(); } } if(fis != null){ try { fis.close(); } catch (IOException e) { e.printStackTrace(); } } } return true; }
Alles sieht so schön aus und scheint es auch zu sein tadellos sein. Daher wurden zwei Testszenariocodes hinzugefügt:
(1) Im selben Prozess konkurrieren zwei Threads gleichzeitig um die Sperre. Es wird vorläufig Testprogramm A genannt. Erwartete Ergebnisse: Ein Thread ist fehlgeschlagen um die Sperre zu erhalten
(2) Führen Sie zwei Prozesse aus, das heißt, führen Sie zwei Testprogramme A aus und erwarten Sie die Ergebnisse: Ein Prozess und ein Thread erhalten die Sperre, und der andere Thread erhält die Sperre nicht
public static void main(String[] args) { new Thread("write-thread-1-lock"){ @Override public void run() { FileLockUtils.lockAndWrite(new File("/data/hello.txt"), "write-thread-1-lock" + System.currentTimeMillis(), false, 30 * 1000);} }.start(); new Thread("write-thread-2-lock"){ @Override public void run() { FileLockUtils.lockAndWrite(new File("/data/hello.txt"), "write-thread-2-lock" + System.currentTimeMillis(), false, 30 * 1000); } }.start(); }
2. Die Welt ist nicht das, was Sie denken
Der obige Testcode kann unsere Erwartungen in einem einzigen Prozess erfüllen . Wenn jedoch zwei Prozesse gleichzeitig ausgeführt werden, kann der zweite Prozess die Sperre normalerweise in der Mac-Umgebung (Java8) erhalten, der zweite Prozess kann die Sperre jedoch nicht in Win7 (Java7) erhalten. Warum? Ist TryLock nicht exklusiv?
Tatsächlich ist TryLock nicht exklusiv, sondern ein Problem mitchannel.close. Die offizielle Aussage lautet:
On some systems, closing a channel releases all locks held by the Java virtual machine on the underlying file regardless of whether the locks were acquired via that channel or via another channel open on the same file.It is strongly recommended that, within a program, a unique channel be used to acquire all locks on any given file.
Der Grund dafür ist, dass in bestimmten Betriebssystemen das Schließen eines Kanals dazu führt, dass die JVM alle Sperren aufhebt. Mit anderen Worten, es ist klar, warum der zweite Testfall oben fehlgeschlagen ist, denn nachdem der zweite Thread des ersten Prozesses die Sperre nicht erhalten konnte, haben wir Channel.close aufgerufen, wodurch alle Sperren aufgehoben und alle zweiten Prozesse freigegeben wurden Die Sperre wird erfolgreich erhalten.
Nach einer mühsamen Reise, um die Wahrheit herauszufinden, habe ich endlich einen Beitrag auf Stackoverflow gefunden, der darauf hinwies, dass Lucences NativeFSLock auch exklusives Schreiben durch mehrere Prozesse erfordert. Der Autor bezieht sich auf den NativeFSLock-Quellcode von Lucence 4.10.4. Die spezifische sichtbare Adresse und die spezifische Abrufmethode lauten wie folgt:
(1) Jede Sperre hat eine lokale entsprechende Datei.
(2) Ein lokaler Thread-sicherer Typ LOCK_HELD verwaltet die Dateipfade aller aktuellen Sperren, um zu verhindern, dass mehrere Threads gleichzeitig Sperren erwerben. Um Sperren zu erhalten, müssen mehrere Threads lediglich feststellen, ob LOCK_HELD bereits vorhanden ist verfügt über einen entsprechenden Dateipfad. Dies bedeutet, dass die Sperre erworben wurde, andernfalls bedeutet dies, dass sie nicht erworben wurde.
(3) Vorausgesetzt, dass LOCK_HELD keinen entsprechenden Dateipfad hat, können Sie TryLock den Dateikanal verwenden.
public synchronized boolean obtain() throws IOException { if (lock != null) { // Our instance is already locked: return false; } // Ensure that lockDir exists and is a directory. if (!lockDir.exists()) { if (!lockDir.mkdirs()) throw new IOException("Cannot create directory: " + lockDir.getAbsolutePath()); } else if (!lockDir.isDirectory()) { // TODO: NoSuchDirectoryException instead? throw new IOException("Found regular file where directory expected: " + lockDir.getAbsolutePath()); } final String canonicalPath = path.getCanonicalPath(); // Make sure nobody else in-process has this lock held // already, and, mark it held if not: // This is a pretty crazy workaround for some documented // but yet awkward JVM behavior: // // On some systems, closing a channel releases all locks held by the // Java virtual machine on the underlying file // regardless of whether the locks were acquired via that channel or via // another channel open on the same file. // It is strongly recommended that, within a program, a unique channel // be used to acquire all locks on any given // file. // // This essentially means if we close "A" channel for a given file all // locks might be released... the odd part // is that we can't re-obtain the lock in the same JVM but from a // different process if that happens. Nevertheless // this is super trappy. See LUCENE-5738 boolean obtained = false; if (LOCK_HELD.add(canonicalPath)) { try { channel = FileChannel.open(path.toPath(), StandardOpenOption.CREATE, StandardOpenOption.WRITE); try { lock = channel.tryLock(); obtained = lock != null; } catch (IOException | OverlappingFileLockException e) { // At least on OS X, we will sometimes get an // intermittent "Permission Denied" IOException, // which seems to simply mean "you failed to get // the lock". But other IOExceptions could be // "permanent" (eg, locking is not supported via // the filesystem). So, we record the failure // reason here; the timeout obtain (usually the // one calling us) will use this as "root cause" // if it fails to get the lock. failureReason = e; } } finally { if (obtained == false) { // not successful - clear up and move // out clearLockHeld(path); final FileChannel toClose = channel; channel = null; closeWhileHandlingException(toClose); } } } return obtained; }
Zusammenfassung
Das obige ist der detaillierte Inhalt vonDetaillierte Code-Erklärung der Java-exklusiven Sperrimplementierung. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Heiße KI -Werkzeuge

Undresser.AI Undress
KI-gestützte App zum Erstellen realistischer Aktfotos

AI Clothes Remover
Online-KI-Tool zum Entfernen von Kleidung aus Fotos.

Undress AI Tool
Ausziehbilder kostenlos

Clothoff.io
KI-Kleiderentferner

AI Hentai Generator
Erstellen Sie kostenlos Ai Hentai.

Heißer Artikel

Heiße Werkzeuge

Notepad++7.3.1
Einfach zu bedienender und kostenloser Code-Editor

SublimeText3 chinesische Version
Chinesische Version, sehr einfach zu bedienen

Senden Sie Studio 13.0.1
Leistungsstarke integrierte PHP-Entwicklungsumgebung

Dreamweaver CS6
Visuelle Webentwicklungstools

SublimeText3 Mac-Version
Codebearbeitungssoftware auf Gottesniveau (SublimeText3)

Heiße Themen



Leitfaden zur perfekten Zahl in Java. Hier besprechen wir die Definition, Wie prüft man die perfekte Zahl in Java?, Beispiele mit Code-Implementierung.

Leitfaden für Weka in Java. Hier besprechen wir die Einführung, die Verwendung von Weka Java, die Art der Plattform und die Vorteile anhand von Beispielen.

Leitfaden zur Smith-Zahl in Java. Hier besprechen wir die Definition: Wie überprüft man die Smith-Nummer in Java? Beispiel mit Code-Implementierung.

In diesem Artikel haben wir die am häufigsten gestellten Fragen zu Java Spring-Interviews mit ihren detaillierten Antworten zusammengestellt. Damit Sie das Interview knacken können.

Java 8 führt die Stream -API ein und bietet eine leistungsstarke und ausdrucksstarke Möglichkeit, Datensammlungen zu verarbeiten. Eine häufige Frage bei der Verwendung von Stream lautet jedoch: Wie kann man von einem Foreach -Betrieb brechen oder zurückkehren? Herkömmliche Schleifen ermöglichen eine frühzeitige Unterbrechung oder Rückkehr, aber die Stream's foreach -Methode unterstützt diese Methode nicht direkt. In diesem Artikel werden die Gründe erläutert und alternative Methoden zur Implementierung vorzeitiger Beendigung in Strahlverarbeitungssystemen erforscht. Weitere Lektüre: Java Stream API -Verbesserungen Stream foreach verstehen Die Foreach -Methode ist ein Terminalbetrieb, der einen Vorgang für jedes Element im Stream ausführt. Seine Designabsicht ist

Anleitung zum TimeStamp to Date in Java. Hier diskutieren wir auch die Einführung und wie man Zeitstempel in Java in ein Datum konvertiert, zusammen mit Beispielen.

Kapseln sind dreidimensionale geometrische Figuren, die aus einem Zylinder und einer Hemisphäre an beiden Enden bestehen. Das Volumen der Kapsel kann berechnet werden, indem das Volumen des Zylinders und das Volumen der Hemisphäre an beiden Enden hinzugefügt werden. In diesem Tutorial wird erörtert, wie das Volumen einer bestimmten Kapsel in Java mit verschiedenen Methoden berechnet wird. Kapselvolumenformel Die Formel für das Kapselvolumen lautet wie folgt: Kapselvolumen = zylindrisches Volumenvolumen Zwei Hemisphäre Volumen In, R: Der Radius der Hemisphäre. H: Die Höhe des Zylinders (ohne die Hemisphäre). Beispiel 1 eingeben Radius = 5 Einheiten Höhe = 10 Einheiten Ausgabe Volumen = 1570,8 Kubikeinheiten erklären Berechnen Sie das Volumen mithilfe der Formel: Volumen = π × R2 × H (4

Java ist eine beliebte Programmiersprache, die sowohl von Anfängern als auch von erfahrenen Entwicklern erlernt werden kann. Dieses Tutorial beginnt mit grundlegenden Konzepten und geht dann weiter zu fortgeschrittenen Themen. Nach der Installation des Java Development Kit können Sie das Programmieren üben, indem Sie ein einfaches „Hello, World!“-Programm erstellen. Nachdem Sie den Code verstanden haben, verwenden Sie die Eingabeaufforderung, um das Programm zu kompilieren und auszuführen. Auf der Konsole wird „Hello, World!“ ausgegeben. Mit dem Erlernen von Java beginnt Ihre Programmierreise, und wenn Sie Ihre Kenntnisse vertiefen, können Sie komplexere Anwendungen erstellen.
