001/* 002 * Copyright (C) 2010 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except 005 * in compliance with the License. You may obtain a copy of the License at 006 * 007 * http://www.apache.org/licenses/LICENSE-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, software distributed under the License 010 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 011 * or implied. See the License for the specific language governing permissions and limitations under 012 * the License. 013 */ 014 015package com.google.common.util.concurrent; 016 017import static com.google.common.base.Preconditions.checkArgument; 018import static com.google.common.base.Preconditions.checkNotNull; 019import static java.util.Objects.requireNonNull; 020import static java.util.concurrent.Executors.defaultThreadFactory; 021 022import com.google.common.annotations.GwtIncompatible; 023import com.google.common.annotations.J2ktIncompatible; 024import com.google.errorprone.annotations.CanIgnoreReturnValue; 025import java.lang.Thread.UncaughtExceptionHandler; 026import java.util.Locale; 027import java.util.concurrent.Executors; 028import java.util.concurrent.ThreadFactory; 029import java.util.concurrent.atomic.AtomicLong; 030import org.jspecify.annotations.Nullable; 031 032/** 033 * A ThreadFactory builder, providing any combination of these features: 034 * 035 * <ul> 036 * <li>whether threads should be marked as {@linkplain Thread#setDaemon daemon} threads 037 * <li>a {@linkplain ThreadFactoryBuilder#setNameFormat naming format} 038 * <li>a {@linkplain Thread#setPriority thread priority} 039 * <li>an {@linkplain Thread#setUncaughtExceptionHandler uncaught exception handler} 040 * <li>a {@linkplain ThreadFactory#newThread backing thread factory} 041 * </ul> 042 * 043 * <p>If no backing thread factory is provided, a default backing thread factory is used as if by 044 * calling {@code setThreadFactory(}{@link Executors#defaultThreadFactory()}{@code )}. 045 * 046 * <p><b>Java 21+ users:</b> consider using the {@code Thread.Builder} interface instead. E.g., 047 * instead of {@code new ThreadFactoryBuilder().setPriority(priority).setDaemon(false).build()}, use 048 * {@code Thread.ofPlatform().priority(priority).daemon(false).factory()}. 049 * 050 * @author Kurt Alfred Kluever 051 * @since 4.0 052 */ 053@J2ktIncompatible 054@GwtIncompatible 055public final class ThreadFactoryBuilder { 056 private @Nullable String nameFormat = null; 057 private @Nullable Boolean daemon = null; 058 private @Nullable Integer priority = null; 059 private @Nullable UncaughtExceptionHandler uncaughtExceptionHandler = null; 060 private @Nullable ThreadFactory backingThreadFactory = null; 061 062 /** 063 * Creates a new {@link ThreadFactory} builder. 064 * 065 * <p><b>Java 21+ users:</b> use {@link Thread#ofPlatform()} instead, translating other calls on 066 * the builder as documented on each method (except for the rarely used {@link #setThreadFactory}, 067 * which does not have an equivalent). 068 */ 069 public ThreadFactoryBuilder() {} 070 071 /** 072 * Sets the naming format to use when naming threads ({@link Thread#setName}) which are created 073 * with this ThreadFactory. 074 * 075 * <p><b>Java 21+ users:</b> use {@link Thread.Builder#name(String, long)} instead. Note that 076 * {@link #setNameFormat} accepts a thread name <i>format string</i> (e.g., {@code 077 * threadFactoryBuilder.setNameFormat("rpc-pool-%d")}), while {@code threadBuilder.name()} accepts 078 * a thread name <i>prefix</i> and initial counter value (e.g., {@code 079 * threadBuilder.name("rpc-pool-", 0)}. 080 * 081 * @param nameFormat a {@link String#format(String, Object...)}-compatible format String, to which 082 * a unique integer (0, 1, etc.) will be supplied as the single parameter. This integer will 083 * be unique to the built instance of the ThreadFactory and will be assigned sequentially. For 084 * example, {@code "rpc-pool-%d"} will generate thread names like {@code "rpc-pool-0"}, {@code 085 * "rpc-pool-1"}, {@code "rpc-pool-2"}, etc. 086 * @return this for the builder pattern 087 */ 088 @CanIgnoreReturnValue 089 public ThreadFactoryBuilder setNameFormat(String nameFormat) { 090 String unused = format(nameFormat, 0); // fail fast if the format is bad or null 091 this.nameFormat = nameFormat; 092 return this; 093 } 094 095 /** 096 * Sets daemon or not for new threads created with this ThreadFactory. 097 * 098 * <p><b>Java 21+ users:</b> use {@link Thread.Builder.OfPlatform#daemon(boolean)} instead. 099 * 100 * @param daemon whether or not new Threads created with this ThreadFactory will be daemon threads 101 * @return this for the builder pattern 102 */ 103 @CanIgnoreReturnValue 104 public ThreadFactoryBuilder setDaemon(boolean daemon) { 105 this.daemon = daemon; 106 return this; 107 } 108 109 /** 110 * Sets the priority for new threads created with this ThreadFactory. 111 * 112 * <p><b>Warning:</b> relying on the thread scheduler is <a 113 * href="http://errorprone.info/bugpattern/ThreadPriorityCheck">discouraged</a>. 114 * 115 * <p><b>Java 21+ users:</b> use {@link Thread.Builder.OfPlatform#priority(int)} instead. 116 * 117 * @param priority the priority for new Threads created with this ThreadFactory 118 * @return this for the builder pattern 119 */ 120 @CanIgnoreReturnValue 121 public ThreadFactoryBuilder setPriority(int priority) { 122 // Thread#setPriority() already checks for validity. These error messages 123 // are nicer though and will fail-fast. 124 checkArgument( 125 priority >= Thread.MIN_PRIORITY, 126 "Thread priority (%s) must be >= %s", 127 priority, 128 Thread.MIN_PRIORITY); 129 checkArgument( 130 priority <= Thread.MAX_PRIORITY, 131 "Thread priority (%s) must be <= %s", 132 priority, 133 Thread.MAX_PRIORITY); 134 this.priority = priority; 135 return this; 136 } 137 138 /** 139 * Sets the {@link UncaughtExceptionHandler} for new threads created with this ThreadFactory. 140 * 141 * <p><b>Java 21+ users:</b> use {@link 142 * Thread.Builder#uncaughtExceptionHandler(Thread.UncaughtExceptionHandler)} instead. 143 * 144 * @param uncaughtExceptionHandler the uncaught exception handler for new Threads created with 145 * this ThreadFactory 146 * @return this for the builder pattern 147 */ 148 @CanIgnoreReturnValue 149 public ThreadFactoryBuilder setUncaughtExceptionHandler( 150 UncaughtExceptionHandler uncaughtExceptionHandler) { 151 this.uncaughtExceptionHandler = checkNotNull(uncaughtExceptionHandler); 152 return this; 153 } 154 155 /** 156 * Sets the backing {@link ThreadFactory} for new threads created with this ThreadFactory. Threads 157 * will be created by invoking #newThread(Runnable) on this backing {@link ThreadFactory}. 158 * 159 * @param backingThreadFactory the backing {@link ThreadFactory} which will be delegated to during 160 * thread creation. 161 * @return this for the builder pattern 162 * @see MoreExecutors 163 */ 164 @CanIgnoreReturnValue 165 public ThreadFactoryBuilder setThreadFactory(ThreadFactory backingThreadFactory) { 166 this.backingThreadFactory = checkNotNull(backingThreadFactory); 167 return this; 168 } 169 170 /** 171 * Returns a new thread factory using the options supplied during the building process. After 172 * building, it is still possible to change the options used to build the ThreadFactory and/or 173 * build again. State is not shared amongst built instances. 174 * 175 * <p><b>Java 21+ users:</b> use {@link Thread.Builder#factory()} instead. 176 * 177 * @return the fully constructed {@link ThreadFactory} 178 */ 179 public ThreadFactory build() { 180 return doBuild(this); 181 } 182 183 // Split out so that the anonymous ThreadFactory can't contain a reference back to the builder. 184 // At least, I assume that's why. TODO(cpovirk): Check, and maybe add a test for this. 185 @SuppressWarnings("ThreadPriorityCheck") // We only propagate user requests (which we discourage). 186 private static ThreadFactory doBuild(ThreadFactoryBuilder builder) { 187 String nameFormat = builder.nameFormat; 188 Boolean daemon = builder.daemon; 189 Integer priority = builder.priority; 190 UncaughtExceptionHandler uncaughtExceptionHandler = builder.uncaughtExceptionHandler; 191 ThreadFactory backingThreadFactory = 192 (builder.backingThreadFactory != null) 193 ? builder.backingThreadFactory 194 : defaultThreadFactory(); 195 AtomicLong count = (nameFormat != null) ? new AtomicLong(0) : null; 196 return new ThreadFactory() { 197 @Override 198 public Thread newThread(Runnable runnable) { 199 Thread thread = backingThreadFactory.newThread(runnable); 200 // TODO(b/139735208): Figure out what to do when the factory returns null. 201 requireNonNull(thread); 202 if (nameFormat != null) { 203 // requireNonNull is safe because we create `count` if (and only if) we have a nameFormat. 204 thread.setName(format(nameFormat, requireNonNull(count).getAndIncrement())); 205 } 206 if (daemon != null) { 207 thread.setDaemon(daemon); 208 } 209 if (priority != null) { 210 thread.setPriority(priority); 211 } 212 if (uncaughtExceptionHandler != null) { 213 thread.setUncaughtExceptionHandler(uncaughtExceptionHandler); 214 } 215 return thread; 216 } 217 }; 218 } 219 220 // weird case in which users pass the format string but not the associated arguments 221 @SuppressWarnings("AnnotateFormatMethod") 222 private static String format(String format, Object... args) { 223 return String.format(Locale.ROOT, format, args); 224 } 225}