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}