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!