2009-11-09 4 views
11

Lors de l'utilisation d'une connexion de base de données locale de thread, la fermeture de la connexion est requise lorsque le thread existe.Comment forcer un thread Java à fermer une connexion de base de données locale de thread

Je ne peux le faire que si je peux remplacer la méthode run() du thread appelant. Même ce n'est pas une bonne solution, car au moment de la sortie, je ne sais pas si une connexion a déjà été ouverte par ce thread.

Le problème est en fait plus général: Comment forcer un thread à appeler une méthode de finalisation d'un objet thread-local quand il se termine.

J'ai regardé les sources de java 1.5 et trouvé que la carte locale de thread est définie sur null, ce qui entraînera éventuellement la garbage collection à appeler finalize(), mais je ne veux pas compter sur les ordures collectionneur.

La dérogation suivante semble inévitable de se assurer qu'une connexion de base de données est fermée:

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

version() ferme la connexion de base de données, si elle a été ouverte. Mais nous ne savons pas si le thread a déjà utilisé ce thread-local. Si get() n'a jamais été appelé par ce fil, alors il est tout à fait un gaspillage d'efforts ici: ThreadLocal.initialValue() sera appelée, une carte sera créée sur ce fil, etc.

-

d'autres précisions et exemple selon le commentaire de Thorbjørn:

java.lang.ThreadLocal est un type d'usine pour un objet qui est lié à un fil. Ce type a un getter pour l'objet et une méthode d'usine (typiquement écrite par l'utilisateur). Lorsque le getter est appelé, il appelle la méthode factory uniquement s'il n'a jamais été appelé auparavant par ce thread.

L'utilisation de ThreadLocal permet à un développeur de lier une ressource à un thread même si le code de thread a été écrit par un tiers. Exemple: Exemple: Disons que nous avons un type de ressource appelé MyType et que nous voulons en avoir un et un seul par thread.

Définir dans la classe en utilisant: private static ThreadLocal resourceFactory = new ThreadLocal() { @Override protégé MyType initialValue() {return new MyType(); }}

utilisation dans le contexte local dans cette classe: public void someMethod() { MyType ressources = resourceFactory.get(); resource.useResource(); }

get() peut appeler initialValue() une seule fois dans le cycle de vie du fil d'appel. À ce stade, une instance de MyType est instanciée et liée à ce thread. Les appels suivants à get() par ce fil se réfèrent à nouveau à cet objet.

L'exemple d'utilisation classique est lorsque MyType est un formateur de texte/date/xml peu sûr.

Mais ces formatteurs habituellement ne doivent pas nécessairement être libéré ou fermé, les connexions de base de données et je ne me sers java.lang.ThreadLocal d'avoir une connexion d'une base de données par thread. La façon dont je le vois, java.lang.ThreadLocal est presque parfait pour cela. Presque parce qu'il n'y a aucun moyen de garantir la fermeture de la ressource si le thread appelant appartient à une application tierce.

J'ai besoin de vos cerveaux: En étendant java.lang.ThreadLocal J'ai réussi à lier une connexion de base de données pour chaque thread, pour son usage exclusif - y compris les threads que je ne peux pas modifier ou remplacer. J'ai réussi à m'assurer que les connexions soient fermées au cas où le thread meurt sur une exception non interceptée.

En cas de sortie de fil normale, le ramasse-miettes ferme la connexion (parce que MyType remplace la finalisation ()). En réalité, cela arrive assez rapidement, mais ce n'est pas idéal.

Si j'avais mon chemin, il y aurait eu une autre méthode sur la java.lang.ThreadLocal:

protected void release() throws Throwable {} 

Si cette méthode existait sur java.lang.ThreadLocal, appelé par machine virtuelle Java sur n'importe quel fil de sortie/mort, alors dans mon propre override je pourrais fermer ma connexion (et le rédempteur serait venu à Sion). En l'absence d'une telle méthode, je suis à la recherche d'une autre façon de confirmer la fermeture.

Une méthode qui ne dépendra pas de la récupération de place JVM.

Merci

+0

S'il vous plaît ajouter un code minimal montrant ce que vous décrivez. De préférence, runnable. –

+0

Ajout de code. Espérons que cela explique l'un des problèmes. –

+0

Eh bien, non. Veuillez créer un exemple fonctionnel minimal. –

Répondre

11

Si vous êtes d'une disposition sensible, détournez le regard maintenant.

Je ne m'attendrais pas à ce que cela évolue très bien; il double efficacement le nombre de threads dans le système. Il peut y avoir des cas d'utilisation où c'est acceptable.

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(); 
    } 
} 

Une meilleure gestion des erreurs et ainsi de suite est laissée à titre d'exercice pour l'implémenteur.

Vous pouvez également jeter un œil au suivi des threads/ressources dans un ConcurrentHashMap avec un autre thread d'interrogation isAlive. Mais cette solution est le dernier recours du désespéré - les objets finiront probablement par être contrôlés trop souvent ou trop rarement.

Je ne peux pas penser à autre chose qui n'implique pas d'instrumentation. AOP pourrait fonctionner.

Le regroupement de connexions serait mon option préférée.

+0

Bonne idée! Cela doublerait le nombre de threads mais je crois que nous pouvons gérer cela. Interroger les threads est hors de question. Si je garde une carte des discussions, je pourrais tout aussi bien supprimer ThreadLocal. Merci –

+0

Je pourrais discuter au sujet de la mise en commun de connexion. Cependant, la connexion à la base de données n'est qu'un exemple. Le problème le plus général est que le système ThreadLocal de Java est paralysé par son incapacité à garantir une libération immédiate des ressources lorsqu'un thread se termine. Pour cette raison, il est moins que parfait pour les ressources qui doivent être libérées. –

+0

Je comprends, mais laisser les ressources ouvertes jusqu'à ce que les filières soient aussi moins qu'optimales dans (dans la plupart des cas) la plupart des situations. Par exemple, sur un serveur Java EE, il est possible que les threads soient regroupés et réutilisés pour de nombreuses unités de travail (demandes de servlet, transactions EJB, etc.) qui peuvent être rarement ou accessoirement utilisées par votre chemin de code. Ainsi, la machine virtuelle peut accumuler de nombreuses connexions libres et inactives. Cette solution doit être rarement utilisée, et uniquement lorsque vous connaissez ou contrôlez les détails de création/utilisation de threads. – McDowell

6

Enveloppez votre Runnable avec une nouvelle Runnable avec une construction

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

, et que cela soit exécuté à la place.

Vous pouvez même en faire une méthode, ex:

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

et appeler cette méthode dans le passage runnable que vous recherchez.

Vous pouvez également envisager d'utiliser un Executor à la place. Rend beaucoup plus facile de gérer Runable et Callables.

Si vous utilisez un ExecutorService, vous pouvez l'utiliser comme executor.submit(closingRunnable(normalRunnable))

Si vous savez que vous mettrez fin à votre entière ExecutorService et que vous voulez des coupures de connexion à ce moment-là, vous pouvez définir une usine de fil fait aussi la fin « après toutes les tâches sont effectuées et l'arrêt a appelé l'exécuteur testamentaire », par exemple:

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; 
    } 

Quant à savoir si ou non doMandatoryStuff peut savoir si oui ou non la connexion n'a jamais été ouvert ou non auparavant, une chose qui vient à l'esprit est d'avoir un deuxième ThreadLocal qui ne fait que suivre si elle a été ouverte ou pas (ex: quand la connexion est ouverte, obtenez alors un AtomicInteger à 2, au moment du nettoyage, vérifiez s'il est toujours à sa valeur par défaut, disons, 1 ...)

+0

Merci. En effet, j'enveloppe la course où je peux. Il y a encore deux problèmes ici: 1. Certains threads qui utilisent l'objet thread-local sont des threads de tierce partie et je n'ai aucun moyen d'envelopper leur exécution() 2. La finalisation (ce que vous appelez mandatoryStuff) nécessite l'accès au objet local-thread. Hélas, s'il n'a jamais été accédé par ce thread, alors l'objet doit être instancié et initialisé. Dans ce cas, une connexion à la base de données sera ouverte ... seulement pour qu'elle puisse être fermée. (J'ai trouvé un moyen de contourner cela - assez moche cependant) Y at-il un moyen de vérifier si un thread local a été initialisé? –

+0

Je ne suis pas certain de comprendre parfaitement ce que vous décrivez. Pouvez-vous créer un exemple de travail minimaliste? –

1

Vous deviez ouvrir la connexion une seule fois, donc vous devez également gérer la fermeture au même endroit. En fonction de votre environnement, les threads peuvent être réutilisés et vous ne pouvez pas vous attendre à ce que le thread soit collecté avant l'arrêt de l'application.

+0

Rien à redire avec votre suggestion. Cependant, très souvent, vous n'aurez pas accès au début et à la fin d'une méthode run(). Très souvent, vous allez implémenter une méthode appelée par une api ou un cadre tiers.C'est un cas où ThreadLocal peut devenir utile et c'est le cas où vous pourriez vous demander comment vous assurer qu'une ressource ThreadLocal ne sera pas laissée à la fermeture du GC. –

1

Je pense que dans le cas général il n'y a pas de bonne solution pour cela, autre que le classique: Code qui obtient la ressource doit être responsable de la fermeture. Dans le cas spécifique, si vous appelez des threads à ce degré, vous pouvez transmettre la connexion à vos méthodes au début du thread, soit avec une méthode qui prend un paramètre personnalisé, soit via une forme d'injection de dépendances. Puis puisque vous avez le code qui fournit la connexion, vous avez le code qui le supprime. Une Injection dépendante basée sur des annotations peut fonctionner ici, car le code qui ne nécessite pas la connexion n'en aura pas et ne nécessitera donc pas d'être fermé, mais il semblerait que votre conception soit trop lointaine pour être rééquipée quelque chose comme ca.

+0

אחי, si vous êtes familier avec java.lang.ThreadLocal vous savez qu'il vous permet de définir l'instanciation de votre objet mais pas sa finalisation. Il y a bien sûr java.lang.Object.finalize() et je l'utilise, mais pour ce que je sais, il n'est pas appelé par le thread sortant mais seulement plus tard, après sa mort, par le garbage collector. –

+0

@Joel, je pense que nous disons la même chose. Le créateur (le premier appelant de la méthode get) doit fermer la ressource. Tout ce que je dis, c'est que vous pouvez dissocier le créateur du reste du code, même si cela n'a pas de sens de le faire. – Yishai

4

La pratique JDBC normale est que vous fermez le Connection (et Statement et ResultSet) dans le même bloc de méthode que vous l'avez acquis.

Dans le code:

Connection connection = null; 

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

En gardant cela à l'esprit, votre question me fait penser qu'il ya quelque chose de mal dans votre conception. Acquérir et fermer ce type de ressources coûteuses et externes dans les plus brefs délais permettra d'éviter à l'application des fuites de ressources potentielles et des plantages. Si votre objectif initial était d'améliorer les performances de connexion, alors vous devez jeter un oeil à connexion pool. Vous pouvez utiliser par exemple l'API C3P0 pour cela. Ou s'il s'agit d'une application Web, utilisez les fonctionnalités de pool de connexions intégrées du serveur d'applications dans la version DataSource. Consultez la documentation spécifique au serveur d'applications pour plus de détails.

+0

L'exemple ci-dessus - ouverture d'une connexion à la volée - est quelque chose que je souhaite éviter. C'est seulement faisable si vous pouvez faire confiance à un groupe de connexions. L'intention de ma conception est d'avoir une connexion liée à un thread - ainsi chaque thread peut avoir sa seule et unique connexion exclusive, s'il en a besoin. Maintenant, je ne crée pas tous les threads qui pourraient appeler mes requêtes. Par conséquent, je ne peux pas affecter une connexion de base de données à un thread sauf si j'utilise le type java.lang.ThreadLocal. Cela s'est avéré être une bonne solution. Le seul problème est de fermer quand un thread (d'un tiers) sort –

+0

Effacer mais encore à risque de fuites de ressources. Un thread peut se bloquer ou s'exécuter plus longtemps que la connexion ne peut rester ouverte. – BalusC

1

Substituez la méthode get() dans ThreaedLocal afin qu'elle définisse une propriété List dans la sous-classe. Cette propriété peut facilement être interrogée pour déterminer si la méthode get() a été appelée pour un thread particulier. Vous pourriez alors accéder à ThreadLocal pour le nettoyer dans ce cas.

Mis à jour en réponse au commentaire

+0

Merci. De votre réponse, je ne comprends pas comment la nouvelle propriété peut dire quel thread a appelé get() et lequel n'a pas encore. –

+0

Dans ce cas, vous pouvez utiliser une liste pour stocker tous les threads qui accèdent à la ThreadLocal –

3

Je ne comprends pas vraiment pourquoi vous n'utilisez pas la mise en commun de connexion traditionnelle. Mais je suppose que vous avez vos raisons.

Quelle liberté avez-vous? Comme certains frameworks DI supportent des cycles de vie d'objet et des variables à fil de discussion (tous bien joliment mandatés). Pourriez-vous utiliser l'un d'entre eux? Je pense que Spring ferait tout ça dès le départ, alors que Guice aurait besoin d'une librairie tierce pour gérer les cycles de vie et les portées de fils.

Ensuite combien de contrôle avez-vous sur la création de la variable ThreadLocal ou la création de threads? Je suppose que vous avez un contrôle complet sur le ThreadLocal mais limité à aucun sur la création de threads?

Pourriez-vous utiliser la programmation orientée aspect pour surveiller le nouveau Runnable ou Threads en étendant la méthode run() pour inclure le nettoyage? Vous devrez également étendre le ThreadLocal afin qu'il puisse s'enregistrer.

1

Ce que nous avons fait était

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

Fondamentalement juste toujours (peut-être ouvert, puis) ​​fermer pour tous les chemins à travers le fil. Dans le cas où cela aide quelqu'un là-bas :)

Questions connexes