Quelqu'un peut-il expliquer brièvement comment et quand utiliser un threadfactory? Un exemple avec et sans utilisation de ThreadFactory peut être très utile pour comprendre les différences.
Merci!
Le modèle d'usine est un modèle de création utilisé dans le développement logiciel pour encapsuler les processus impliqués dans la création d'objets.
Supposons que nous avons des threads de travail pour différentes tâches et que nous les voulons avec des noms spéciaux (par exemple, à des fins de débogage). Nous pourrions donc implémenter un ThreadFactory:
public class WorkerThreadFactory implements ThreadFactory {
private int counter = 0;
private String prefix = "";
public WorkerThreadFactory(String prefix) {
this.prefix = prefix;
}
public Thread newThread(Runnable r) {
return new Thread(r, prefix + "-" + counter++);
}
}
Si vous aviez une telle exigence, il serait assez difficile de l'implémenter sans modèle d'usine ou constructeur.
ThreadFactory
fait partie de l'API Java car il est également utilisé par d'autres classes. Ainsi, l’exemple ci-dessus montre pourquoi nous devrions parfois utiliser «une usine pour créer des threads», mais il n’est bien entendu absolument pas nécessaire de mettre en œuvre Java.util.concurrent.ThreadFactory
pour accomplir cette tâche.
Voici une utilisation possible. Si vous disposez d'un service exécuteur qui exécute vos tâches exécutables de manière multithread et que de temps en temps votre thread meurt d'une exception non interceptée. Supposons que vous ne souhaitiez pas enregistrer toutes ces exceptions. ThreadFactory
résout ce problème:
ExecutorService executor = Executors.newSingleThreadExecutor(new LoggingThreadFactory());
executor.submit(new Runnable() {
@Override
public void run() {
someObject.someMethodThatThrowsRuntimeException();
}
});
LoggingThreadFactory
peut être implémenté comme celui-ci:
public class LoggingThreadFactory implements ThreadFactory
{
@Override
public Thread newThread(Runnable r)
{
Thread t = new Thread(r);
t.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler()
{
@Override
public void uncaughtException(Thread t, Throwable e)
{
LoggerFactory.getLogger(t.getName()).error(e.getMessage(), e);
}
});
return t;
}
}
Quelques rouages
Le sujet est assez bien couvert, à l'exception de quelques travaux internes qui ne sont pas facilement visibles . Lors de la création d'un thread avec le constructeur, le thread nouvellement créé hérite des threads actuels:
ThreadGroup
(sauf si fourni ou System.getSecurityManager().getThreadGroup()
renvoie ThreadGroup
arbitraire) - Le groupe de threads lui-même peut être important dans certains cas et peut entraîner une interruption/interruption incorrecte du thread. La ThreadGroup
sera le gestionnaire d'exceptions par défaut.ContextClassLoader
- dans un environnement géré, cela ne devrait pas être un gros problème, car l'environnement doit basculer le CCL, mais si vous voulez l'implémenter, gardez cela à l'esprit. La fuite du CCL de l'appelant est assez mauvaise, tout comme le groupe de threads (en particulier si le threadGroup est une sous-classe et non pas un Java.lang.ThreadGroup
direct - il est nécessaire de remplacer le ThreadGroup.uncaughtException
)AccessControlContext
- ici, il n'y a pratiquement rien à faire (à part démarrer dans un thread dédié) car le champ est réservé à un usage interne, et très peu soupçonnent même l'existence de.InheritableThreadLocal
de l'appelant, ce qui peut (ou non) avoir des conséquences. Encore une fois, rien ne peut être fait, à part créer le fil dans un fil dédié.En fonction de l'application, les points ci-dessus peuvent n'avoir aucun effet, mais dans certains cas, certains d'entre eux peuvent entraîner des fuites de classe/ressources difficiles à détecter et présentant un comportement non déterministe.
Cela ferait un très long post mais si ...
Ci-dessous, quelques exemples (espérons-le) de code réutilisable pour ThreadFactory
implementation, il peut être utilisé dans des environnements gérés pour garantir que ThreadGroup
(qui peut limiter la priorité ou interrompre les threads), ContextClassLoader
, stacksize, etc. être configuré) et non fui. S'il y a un quelconque intérêt, je peux montrer comment traiter avec ThreadLocals
hérité ou l'accusé hérité (qui peut essentiellement faire fuir l'appel classloader
)
package bestsss.util;
import Java.lang.Thread.UncaughtExceptionHandler;
import Java.security.AccessControlContext;
import Java.security.AccessController;
import Java.security.PrivilegedAction;
import Java.util.concurrent.ThreadFactory;
import Java.util.concurrent.atomic.AtomicLong;
public class ThreadFactoryX implements ThreadFactory{
//thread properties
long stackSize;
String pattern;
ClassLoader ccl;
ThreadGroup group;
int priority;
UncaughtExceptionHandler exceptionHandler;
boolean daemon;
private boolean configured;
private boolean wrapRunnable;//if acc is present wrap or keep it
protected final AccessControlContext acc;
//thread creation counter
protected final AtomicLong counter = new AtomicLong();
public ThreadFactoryX(){
final Thread t = Thread.currentThread();
ClassLoader loader;
AccessControlContext acc = null;
try{
loader = t.getContextClassLoader();
if (System.getSecurityManager()!=null){
acc = AccessController.getContext();//keep current permissions
acc.checkPermission(new RuntimePermission("setContextClassLoader"));
}
}catch(SecurityException _skip){
//no permission
loader =null;
acc = null;
}
this.ccl = loader;
this.acc = acc;
this.priority = t.getPriority();
this.daemon = true;//Executors have it false by default
this.wrapRunnable = true;//by default wrap if acc is present (+SecurityManager)
//default pattern - caller className
StackTraceElement[] stack = new Exception().getStackTrace();
pattern(stack.length>1?getOuterClassName(stack[1].getClassName()):"ThreadFactoryX", true);
}
public ThreadFactory finishConfig(){
configured = true;
counter.addAndGet(0);//write fence "w/o" volatile
return this;
}
public long getCreatedThreadsCount(){
return counter.get();
}
protected void assertConfigurable(){
if (configured)
throw new IllegalStateException("already configured");
}
private static String getOuterClassName(String className){
int idx = className.lastIndexOf('.')+1;
className = className.substring(idx);//remove package
idx = className.indexOf('$');
if (idx<=0){
return className;//handle classes starting w/ $
}
return className.substring(0,idx);//assume inner class
}
@Override
public Thread newThread(Runnable r) {
configured = true;
final Thread t = new Thread(group, wrapRunnable(r), composeName(r), stackSize);
t.setPriority(priority);
t.setDaemon(daemon);
t.setUncaughtExceptionHandler(exceptionHandler);//securityException only if in the main group, shall be safe here
//funny moment Thread.getUncaughtExceptionHandler() has a race.. badz (can throw NPE)
applyCCL(t);
return t;
}
private void applyCCL(final Thread t) {
if (ccl!=null){//use factory creator ACC for setContextClassLoader
AccessController.doPrivileged(new PrivilegedAction<Object>(){
@Override
public Object run() {
t.setContextClassLoader(ccl);
return null;
}
}, acc);
}
}
private Runnable wrapRunnable(final Runnable r){
if (acc==null || !wrapRunnable){
return r;
}
Runnable result = new Runnable(){
public void run(){
AccessController.doPrivileged(new PrivilegedAction<Object>(){
@Override
public Object run() {
r.run();
return null;
}
}, acc);
}
};
return result;
}
protected String composeName(Runnable r) {
return String.format(pattern, counter.incrementAndGet(), System.currentTimeMillis());
}
//standard setters allowing chaining, feel free to add normal setXXX
public ThreadFactoryX pattern(String patten, boolean appendFormat){
assertConfigurable();
if (appendFormat){
patten+=": %d @ %tF %<tT";//counter + creation time
}
this.pattern = patten;
return this;
}
public ThreadFactoryX daemon(boolean daemon){
assertConfigurable();
this.daemon = daemon;
return this;
}
public ThreadFactoryX priority(int priority){
assertConfigurable();
if (priority<Thread.MIN_PRIORITY || priority>Thread.MAX_PRIORITY){//check before actual creation
throw new IllegalArgumentException("priority: "+priority);
}
this.priority = priority;
return this;
}
public ThreadFactoryX stackSize(long stackSize){
assertConfigurable();
this.stackSize = stackSize;
return this;
}
public ThreadFactoryX threadGroup(ThreadGroup group){
assertConfigurable();
this.group= group;
return this;
}
public ThreadFactoryX exceptionHandler(UncaughtExceptionHandler exceptionHandler){
assertConfigurable();
this.exceptionHandler= exceptionHandler;
return this;
}
public ThreadFactoryX wrapRunnable(boolean wrapRunnable){
assertConfigurable();
this.wrapRunnable= wrapRunnable;
return this;
}
public ThreadFactoryX ccl(ClassLoader ccl){
assertConfigurable();
this.ccl = ccl;
return this;
}
}
Aussi, quelques utilisations très simples:
ThreadFactory factory = new TreadFactoryX().priority(3).stackSize(0).wrapRunnable(false).pattern("Socket workers", true).
daemon(false).finishConfig();
IMHO la fonction la plus importante d'une ThreadFactory
est de nommer les threads quelque chose d'utile. Avoir des threads dans un stacktrace nommé pool-1-thread-2
ou pire Thread-12
est une douleur complète lors du diagnostic des problèmes.
Bien sûr, le fait d'avoir ThreadGroup
, le statut et la priorité d'un démon sont également utiles.
Utilisation de ThreadFactory en Java
Un objet qui crée de nouveaux threads à la demande. L'utilisation de fabriques de threads supprime le câblage des appels à new Thread
, ce qui permet aux applications d'utiliser des sous-classes de thread, des priorités, etc.
La mise en oeuvre la plus simple de cette interface est la suivante:
class SimpleThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
return new Thread(r);
}
}
DefaultThreadFactory de ThreadPoolExecutor.Java
static class DefaultThreadFactory implements ThreadFactory {
private static final AtomicInteger poolNumber = new AtomicInteger(1);
private final ThreadGroup group;
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
DefaultThreadFactory() {
SecurityManager s = System.getSecurityManager();
group = (s != null) ? s.getThreadGroup() :
Thread.currentThread().getThreadGroup();
namePrefix = "pool-" +
poolNumber.getAndIncrement() +
"-thread-";
}
public Thread newThread(Runnable r) {
Thread t = new Thread(group, r,
namePrefix + threadNumber.getAndIncrement(),
0);
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
Il est toujours recommandé d'utiliser une fabrique de threads personnalisée. Les usines par défaut ne sont pas très utiles. Vous devez utiliser une fabrique personnalisée pour les raisons suivantes:
Vérifiez ce post: http://wilddiary.com/understanding-Java-threadfactory-creating-custom-thread-factories/
Comme mentionné par "InsertNickHere", vous devrez comprendre le Factory Pattern .
Un bon exemple d'utilisation d'une ThreadFactory est le ThreadPoolExecutor : L'exécuteur créera des Threads si nécessaire et s'occupera du pooling. Si vous souhaitez intervenir dans le processus de création et attribuer des noms spéciaux aux threads créés, ou les affecter à un groupe de threads, vous pouvez créer une unité de threads à cette fin et le donner à l'exécuteur.
C'est un peu IoC - style.
ThreadFactory est une interface avec une seule méthode public abstract Java.lang.Thread newThread(Java.lang.Runnable arg0);
Son utilisation dépend de vos besoins. Supposons que vous souhaitiez qu'une fonctionnalité particulière crée toujours des threads Daemon. Vous pouvez facilement y parvenir avec ThreadFactory.
Le code ci-dessous est juste pour dire le fondamental. Il ne fait aucune fonctionnalité spécifique.
package TestClasses;
import Java.util.concurrent.ThreadFactory;
public class ThreadFactoryEx implements ThreadFactory{
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
}
package TestClasses;
import Java.util.concurrent.ThreadPoolExecutor;
public class RunnableEx implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for (int i = 0; i < 5; i++) {
System.out.println("in a loop" + i + "times");
}
}
}
package TestClasses;
import Java.util.concurrent.ExecutorService;
import Java.util.concurrent.Executors;
public class Thread1 {
public static void main(String[] args) {
ExecutorService exe = Executors.newCachedThreadPool(new ThreadFactoryEx());
for (int i = 0; i < 4; i++) {
exe.execute(new RunnableEx());
}
}
}
ThreadFactory
sera utile
Vous pouvez utiliser ThreadFactoryBuilder
à partir de Google Guava Lib pour créer ThreadFactory
comme ceci
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("MyThreadPool-Worker-%d")
.setDaemon(true)
.build();
Examinez VerboseThreads
(implémente ThreadFactory
) à partir de jcabi-log . Cette implémentation crée Thread
s qui enregistre les exceptions lorsqu'elles sont supprimées. Classe très utile lorsque vous avez besoin de savoir quand et pourquoi vos fils meurent.