Kann jemand kurz erklären, wie und wann eine ThreadFactory verwendet wird? Ein Beispiel mit und ohne ThreadFactory kann hilfreich sein, um die Unterschiede zu verstehen.
Vielen Dank!
Das Factory-Muster ist ein kreatives Entwurfsmuster, das in der Softwareentwicklung verwendet wird, um die Prozesse, die bei der Erstellung von Objekten involviert sind, zu kapseln.
Nehmen wir an, wir haben einige Worker-Threads für verschiedene Aufgaben und möchten, dass sie mit speziellen Namen versehen werden (beispielsweise zu Debugging-Zwecken). So könnten wir eine ThreadFactory implementieren:
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++);
}
}
Wenn Sie eine solche Anforderung hätten, wäre es ziemlich schwierig, sie ohne Factory- oder Builder-Pattern zu implementieren.
ThreadFactory
ist Teil der Java-API, da sie auch von anderen Klassen verwendet wird. Das obige Beispiel zeigt also, warum wir in manchen Fällen "eine Factory zum Erstellen von Threads" verwenden sollten. Natürlich ist es nicht notwendig, Java.util.concurrent.ThreadFactory
für diese Aufgabe zu implementieren.
Hier ist eine mögliche Verwendung. Wenn Sie über einen Executor-Service verfügen, der Ihre ausführbaren Aufgaben auf Multithreading-Weise ausführt, und von Zeit zu Zeit stirbt Ihr Thread an einer nicht erfassten Ausnahme. Nehmen wir an, Sie wollen all diese Ausnahmen nicht protokollieren. ThreadFactory
löst dieses Problem:
ExecutorService executor = Executors.newSingleThreadExecutor(new LoggingThreadFactory());
executor.submit(new Runnable() {
@Override
public void run() {
someObject.someMethodThatThrowsRuntimeException();
}
});
LoggingThreadFactory
kann wie folgt implementiert werden:
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;
}
}
Einige innere Funktionen
Das Thema wird ziemlich gut behandelt, mit Ausnahme einiger innerer Arbeiten, die nicht leicht sichtbar sind .. Beim Erstellen eines Threads mit dem Konstruktor erbt der neu erstellte Thread aktuelle Threads:
ThreadGroup
(sofern nicht angegeben oder System.getSecurityManager().getThreadGroup()
willkürlich _ zurückgibt ThreadGroup
) - Die Thread-Gruppe selbst kann in einigen Fällen wichtig sein und zu einem falschen Thread-Abbruch/-Abbruch führen. ThreadGroup
wird als Standard-Ausnahmehandler verwendet.ContextClassLoader
- in einer verwalteten Umgebung sollte dies kein großes Problem sein, da die Umgebung die CCL wechseln soll, aber wenn Sie dies implementieren möchten, denken Sie daran. Das Durchsickern der CCL des Aufrufers ist ziemlich schlecht. Dies gilt auch für die Thread-Gruppe (insbesondere wenn die ThreadGroup eine Unterklasse ist und nicht direkt Java.lang.ThreadGroup
- muss ThreadGroup.uncaughtException
überschrieben werden.)AccessControlContext
- Hier gibt es praktisch nichts zu tun (außer dem Start in einem dedizierten Thread), da das Feld nur für den internen Gebrauch bestimmt ist und nur wenige das Vorhandensein von.InheritableThreadLocal
des Aufrufers - was (möglicherweise) nicht zu einigen Auswirkungen führen kann. Wieder kann nichts getan werden, außer den Thread in einen dedizierten Thread zu laichen.Abhängig von der Anwendung können die oben genannten Punkte überhaupt keine Auswirkungen haben, aber in einigen Fällen können einige von ihnen zu Klassen-/Ressourcenlecks führen, die schwer zu erkennen sind und kein deterministisches Verhalten zeigen.
Das würde einen extra langen Post machen aber so ...
unten ist ein (hoffentlich) wiederverwendbarer Code für die Implementierung von ThreadFactory
. Er kann in verwalteten Umgebungen verwendet werden, um sicherzustellen, dass ThreadGroup
(der Threads auf Priorität oder Interrupt beschränken kann), ContextClassLoader
, stacksize usw. sind eingestellt (und/oder können konfiguriert werden) und werden nicht durchgesickert. Wenn Interesse besteht, kann ich zeigen, wie mit ThreadLocals
vererbt wird oder mit dem geerbten acc (was im Wesentlichen den Aufruf von classloader
durchläuft).
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;
}
}
Auch einige sehr einfache Verwendung:
ThreadFactory factory = new TreadFactoryX().priority(3).stackSize(0).wrapRunnable(false).pattern("Socket workers", true).
daemon(false).finishConfig();
IMHO ist die wichtigste Funktion einer ThreadFactory
das Benennen von Threads etwas Nützliches. Threads in einem Stacktrace namens pool-1-thread-2
oder schlechter Thread-12
sind bei der Diagnose von Problemen ein komplizierter Schmerz.
Natürlich haben auch der Status und die Priorität des Daemons eine ThreadGroup
.
ThreadFactory-Verwendung in Java
Ein Objekt, das bei Bedarf neue Threads erstellt. Durch die Verwendung von Thread-Factorys werden Aufrufe von new Thread
nicht mehr fest verbunden, sodass Anwendungen spezielle Thread-Unterklassen, Prioritäten usw. verwenden können.
Die einfachste Implementierung dieser Schnittstelle ist nur:
class SimpleThreadFactory implements ThreadFactory {
public Thread newThread(Runnable r) {
return new Thread(r);
}
}
DefaultThreadFactory von 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;
}
}
Es ist immer eine gute Praxis, eine benutzerdefinierte Gewindefabrik zu verwenden. Die Standardfabriken sind nicht sehr nützlich. Sie sollten aus folgenden Gründen eine benutzerdefinierte Fabrik verwenden:
Überprüfen Sie diesen Beitrag: http://wilddiary.com/understanding-Java-threadfactory-creating-custom-thread-factories/
Wie von "InsertNickHere" erwähnt, müssen Sie das Factory Pattern verstehen.
Ein gutes Beispiel für die Verwendung einer ThreadFactory ist der ThreadPoolExecutor : Der Executor erstellt bei Bedarf Threads und kümmert sich um das Pooling. Wenn Sie beim Erstellungsprozess einspringen und den erstellten Threads spezielle Namen geben oder einer ThreadGroup zuweisen möchten, können Sie eine ThreadFactory für diesen Zweck erstellen und dem Executor übergeben.
Es ist ein bisschen IoC - Stil.
ThreadFactory ist eine Schnittstelle mit einer einzigen Methode public abstract Java.lang.Thread newThread(Java.lang.Runnable arg0);
Die Verwendung hängt von Ihrer Anforderung ab. Angenommen, Sie möchten, dass eine bestimmte Funktionalität immer Daemon-Threads erstellt. Mit ThreadFactory können Sie dies leicht erreichen.
Der folgende Code dient nur zum Erläutern der Grundlagen. Es führt keine spezifische Funktionalität aus.
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
wird nützlich sein
Sie können ThreadFactoryBuilder
von Google Guava lib verwenden, um ThreadFactory
wie folgt zu erstellen
ThreadFactory threadFactory = new ThreadFactoryBuilder()
.setNameFormat("MyThreadPool-Worker-%d")
.setDaemon(true)
.build();
Werfen Sie einen Blick auf VerboseThreads
(implementiert ThreadFactory
) von jcabi-log . Diese Implementierung macht Thread
s, die Ausnahmen protokolliert, wenn sie aus ihnen herausgeworfen werden. Sehr nützliche Klasse, wenn Sie sehen müssen, wann und warum Ihre Threads sterben.