Frage

Bei Verwendung einer Thread-lokalen Datenbankverbindung ist das Schließen der Verbindung erforderlich, wenn der Thread vorhanden ist.

Dies kann ich nur tun, wenn ich die run()-Methode des aufrufenden Threads überschreiben kann.Selbst das ist keine gute Lösung, da ich zum Zeitpunkt des Beendens nicht weiß, ob von diesem Thread jemals eine Verbindung geöffnet wurde.

Das Problem ist tatsächlich allgemeiner:So erzwingen Sie, dass ein Thread beim Beenden eine Finalisierungsmethode eines Thread-lokalen Objekts aufruft.

Ich habe mir die Quellen von Java 1.5 angesehen und festgestellt, dass die lokale Thread-Zuordnung auf Null gesetzt ist, was schließlich dazu führt, dass die Garbage Collection aufgerufen wird finalisieren(), aber ich möchte nicht auf den Garbage Collector zählen.

Die folgende Überschreibung scheint unvermeidlich, um sicherzustellen, dass eine Datenbankverbindung geschlossen wird:

@Override 
public void remove() {
    get().release(); 
    super.remove(); 
}

Wo freigeben() schließt die Datenbankverbindung, sofern diese geöffnet wurde.Wir wissen jedoch nicht, ob der Thread dieses Thread-Local jemals verwendet hat.Wenn get() noch nie von diesem Thread aufgerufen wurde, dann ist hier ziemliche Aufwandsverschwendung: ThreadLocal.initialValue() wird aufgerufen, eine Karte wird in diesem Thread erstellt usw.

-

Weitere Erläuterung und Beispiel gemäß Thorbjørns Kommentar:

java.lang.ThreadLocal ist eine Art Factory für ein Objekt, das an einen Thread gebunden ist.Dieser Typ verfügt über einen Getter für das Objekt und eine Factory-Methode (normalerweise vom Benutzer geschrieben).Wenn der Getter aufgerufen wird, ruft er die Factory-Methode nur dann auf, wenn sie noch nie zuvor von diesem Thread aufgerufen wurde.

Benutzen ThreadLocal ermöglicht es einem Entwickler, eine Ressource an einen Thread zu binden, selbst wenn der Thread-Code von einem Dritten geschrieben wurde.

Beispiel:Angenommen, wir haben einen Ressourcentyp namens Mein Typ und wir möchten nur eines davon pro Thread haben.

Definieren Sie in der verwendenden Klasse:private static ThreadLocal resourceFactory = new ThreadLocal(){ @override protected MyType initialValue(){ return new MyType();} }

Verwendung im lokalen Kontext in dieser Klasse:public void someMethod(){ MyType resource = resourceFactory.get();resources.useResource();}

erhalten() kann anrufen Ursprünglicher Wert() nur einmal im Lebenszyklus des aufrufenden Threads.An diesem Punkt eine Instanz von Mein Typ wird instanziiert und an diesen Thread gebunden.Nachfolgende Anrufe an erhalten() in diesem Thread noch einmal auf dieses Objekt verweisen.

Das klassische Anwendungsbeispiel ist when Mein Typ ist ein Thread-unsicherer Text-/Datums-/XML-Formatierer.

Aber solche Formatierer müssen normalerweise nicht freigegeben oder geschlossen werden, Datenbankverbindungen tun dies und ich verwende sie java.lang.ThreadLocal um eine Datenbankverbindung pro Thread zu haben.

Wie ich es sehe, java.lang.ThreadLocal ist dafür nahezu perfekt.Fast weil es keine Möglichkeit gibt, das Schließen der Ressource zu garantieren, wenn der aufrufende Thread zu einer Drittanbieteranwendung gehört.

Ich brauche deinen Verstand, Knappen:Durch Erweitern java.lang.ThreadLocal Ich habe es geschafft, eine Datenbankverbindung für jeden Thread zu binden, damit diese ausschließlich genutzt wird – einschließlich Threads, die ich nicht ändern oder überschreiben kann.Ich habe es geschafft, sicherzustellen, dass die Verbindungen geschlossen werden, falls der Thread aufgrund einer nicht abgefangenen Ausnahme stirbt.

Im Falle eines normalen Thread-Ausstiegs schließt der Garbage Collector die Verbindung (weil Mein Typ überschreibt die finalisieren()).Eigentlich geht das recht schnell, aber ideal ist das nicht.

Wenn es nach mir ginge, hätte es eine andere Methode gegeben java.lang.ThreadLocal:

protected void release() throws Throwable {}

Wenn diese Methode existierte java.lang.ThreadLocal, von JVM bei jedem Thread-Ausstieg/-Tod aufgerufen, dann könnte ich durch meine eigene Überschreibung meine Verbindung schließen (und der Erlöser wäre nach Zion gekommen).

In Ermangelung einer solchen Methode suche ich nach einer anderen Möglichkeit, den Abschluss zu bestätigen.Eine Möglichkeit, die nicht auf der JVM-Garbage Collection basiert.

Danke

War es hilfreich?

Lösung

Wenn Sie ein sensibles Gemüt haben, schauen Sie jetzt weg.

Ich würde nicht erwarten, dass sich das sehr gut skalieren lässt;Dadurch wird die Anzahl der Threads im System effektiv verdoppelt.Es kann einige Anwendungsfälle geben, in denen dies akzeptabel ist.

public class Estragon {
  public static class Vladimir {
    Vladimir() { System.out.println("Open"); }
    public void close() { System.out.println("Close");}
  }

  private static ThreadLocal<Vladimir> HOLDER = new ThreadLocal<Vladimir>() {
    @Override protected Vladimir initialValue() {
      return createResource();
    }
  };

  private static Vladimir createResource() {
    final Vladimir resource = new Vladimir();
    final Thread godot = Thread.currentThread();
    new Thread() {
      @Override public void run() {
        try {
          godot.join();
        } catch (InterruptedException e) {
          // thread dying; ignore
        } finally {
          resource.close();
        }
      }
    }.start();
    return resource;
  }

  public static Vladimir getResource() {
    return HOLDER.get();
  }
}

Eine bessere Fehlerbehandlung usw. bleibt dem Implementierer als Übung überlassen.

Sie können auch einen Blick auf die Verfolgung der Threads/Ressourcen in einem werfen ConcurrentHashMap mit einer weiteren Thread-Abfrage ist am Leben.Aber diese Lösung ist der letzte Ausweg der Verzweifelten – Objekte werden wahrscheinlich zu oft oder zu selten überprüft.

Mir fällt nichts anderes ein, bei dem es nicht um Instrumentierung geht.AOP könnte funktionieren.

Verbindungspooling wäre meine bevorzugte Option.

Andere Tipps

Wickeln Sie Ihr Runnable mit einem neuen Runnable mit ein

try {
  wrappedRunnable.run();
} finally {
  doMandatoryStuff();
}

Konstruktion, und lassen Sie DAS stattdessen ausführen.

Sie könnten es sogar in eine Methode umwandeln, z. B.:

  Runnable closingRunnable(Runnable wrappedRunnable) {
    return new Runnable() {
      @Override
      public void run() {
        try {
          wrappedRunnable.run();
        } finally {
          doMandatoryStuff();
        }
      }
    };
  }

und rufen Sie diese Methode auf, indem Sie die gesuchte ausführbare Datei übergeben.

Möglicherweise möchten Sie stattdessen auch die Verwendung eines Executors in Betracht ziehen.Erleichtert die Verwaltung von Runables und Callables erheblich.

Wenn Sie einen ExecutorService verwenden, können Sie ihn wie folgt verwenden executor.submit(closingRunnable(normalRunnable))

Wenn Sie wissen, dass Sie Ihren gesamten ExecutorService herunterfahren und möchten, dass Verbindungen zu diesem Zeitpunkt geschlossen werden, können Sie eine Thread-Factory festlegen, die auch das Schließen ausführt, „nachdem alle Aufgaben erledigt und das Herunterfahren auf dem Executor aufgerufen wurden“, Beispiel:

  ExecutorService autoClosingThreadPool(int numThreads) {
    ThreadPoolExecutor threadPool = new ThreadPoolExecutor(numThreads, numThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()); // same as Executors.newFixedThreadPool
    threadPool.setThreadFactory(new ThreadFactory() {
      @Override
      public Thread newThread(Runnable r) {
        return new Thread(closingRunnable(r)); // closes it when executor is shutdown
      }
    });
    return threadPool;
  }

Im Hinblick darauf, ob doMandatoryStuff wissen kann, ob die Verbindung jemals geöffnet wurde oder nicht, fällt mir ein zweites ThreadLocal ein, das nur verfolgt, ob die Verbindung geöffnet wurde oder nicht (z. B.:Wenn die Verbindung geöffnet wird, holen Sie sich einen AtomicInteger und setzen Sie ihn dann auf 2. Überprüfen Sie beim Bereinigen, ob er immer noch auf dem Standardwert ist, sagen wir 1...)

Die normale JDBC-Praxis besteht darin, dass Sie die schließen Connection (Und Statement Und ResultSet) im selben Methodenblock, in dem Sie ihn erworben haben.

In Code:

Connection connection = null;

try {
    connection = getConnectionSomehow();
    // Do stuff.
} finally {
    if (connection != null) {
        try {
            connection.close();
        } catch (SQLException e) {
            ignoreOrLogItButDontThrowIt(e);
        }
    }
}

Vor diesem Hintergrund lässt mich Ihre Frage vermuten, dass mit Ihrem Design etwas nicht stimmt.Das Beschaffen und Schließen dieser Art teurer und externer Ressourcen in kürzester Zeit schützt die Anwendung vor potenziellen Ressourcenlecks und Abstürzen.

Wenn Ihre ursprüngliche Absicht darin bestand, die Verbindungsleistung zu verbessern, müssen Sie danach suchen Verbindungspooling.Sie können zum Beispiel die verwenden C3P0 API dafür.Oder wenn es sich um eine Webanwendung handelt, nutzen Sie die integrierten Verbindungspooling-Funktionen des App-Servers im Stil einer DataSource.Weitere Informationen finden Sie in der appserverspezifischen Dokumentation.

Ich verstehe nicht wirklich, warum Sie kein herkömmliches Verbindungspooling verwenden.Aber ich gehe davon aus, dass Sie Ihre Gründe haben.

Wie viel Freiheit hast du?Da einige DI-Frameworks Objektlebenszyklen und Thread-Variablen unterstützen (alle gut per Proxy versehen).Könnten Sie eines davon verwenden?Ich denke, Spring würde das alles sofort erledigen, während Guice eine Bibliothek eines Drittanbieters benötigen würde, um Lebenszyklen und Thread-Bereiche zu verwalten.

Wie viel Kontrolle haben Sie als nächstes über die Erstellung der ThreadLocal-Variablen oder die Erstellung von Threads?Ich vermute, Sie haben die vollständige Kontrolle über ThreadLocal, sind aber auf die Erstellung von Threads beschränkt?

Könnten Sie aspektorientierte Programmierung verwenden, um neue Runnables oder Threads zu überwachen und die run()-Methode um die Bereinigung zu erweitern?Sie müssen auch ThreadLocal erweitern, damit es sich selbst registrieren kann.

Da Sie die Verbindung einmal öffnen mussten, müssen Sie an derselben Stelle auch die Schließung vornehmen.Abhängig von Ihrer Umgebung können die Threads wiederverwendet werden und Sie können nicht erwarten, dass der Thread vor dem Herunterfahren der Anwendung durch Garbage Collection erfasst wird.

Ich denke, im Allgemeinen gibt es dafür keine gute Lösung außer dem Klassiker:Code, der die Ressource abruft, muss für deren Schließung verantwortlich sein.

Wenn Sie im konkreten Fall Threads in diesem Umfang aufrufen, können Sie die Verbindung zu Ihren Methoden am Anfang des Threads übergeben, entweder mit einer Methode, die einen benutzerdefinierten Parameter annimmt, oder über eine Form der Abhängigkeitsinjektion.Da Sie dann den Code haben, der die Verbindung herstellt, haben Sie auch den Code, der sie entfernt.

Eine auf Anmerkungen basierende Abhängigkeitsinjektion könnte hier funktionieren, da Code, der die Verbindung nicht benötigt, keine erhält und daher nicht geschlossen werden müsste, aber es hört sich so an, als ob Ihr Design zu weit fortgeschritten ist, um so etwas nachzurüsten.

Überschreiben Sie die get()-Methode in ThreaedLocal, sodass sie eine List-Eigenschaft für die Unterklasse festlegt.Diese Eigenschaft kann einfach abgefragt werden, um festzustellen, ob die Methode get() für einen bestimmten Thread aufgerufen wurde.In diesem Fall könnten Sie dann auf ThreadLocal zugreifen, um es zu bereinigen.

Als Reaktion auf den Kommentar aktualisiert

Was wir getan haben, war

@Override
public void run() {
  try {
    // ...
  } finally {
    resource.close();
  }
}

Im Grunde einfach immer (möglicherweise öffnen und dann) schließen für alle Pfade durch den Thread.Falls es jemandem da draußen hilft :)

Ich sehe das gleiche Problem.Sieht so aus, als müssten Sie bisher finalize() verwenden, obwohl es jetzt veraltet ist.Da die Aufgaben an einen Executor übergeben werden, wissen Sie nie, wann ein Thread genau beendet wird, es sei denn, der Executor zeigt es Ihnen an. Das bedeutet, dass Sie bis zu einem gewissen Grad die Kontrolle über den Executor haben.

Wenn Sie beispielsweise den Executor durch Erweitern von ThreadPoolExecutor erstellen, können Sie die Methoden „afterExecution()“ und „terminated()“ überschreiben, um dies zu erreichen.Ersteres gilt für einen Thread, der abnormal beendet wird, letzteres für normales Beenden.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top