/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.commons.lang.concurrent; import java.util.concurrent.Callable; import java.util.concurrent.CopyOnWriteArrayList; /** * Helps propagating ThreadLocal variables to the child Threads, e.g. when {@link java.util.concurrent.ExecutorService} is in use. * <p/> * Usage example: * <pre> * private static ThreadLocal<MyClass> myThreadLocal = new ThreadLocal<>(); * static { * // register ThreadLocal variable * ThreadLocalPropagateContext.addThreadLocal(myThreadLocal); * } * * private ExecutorService executor = ...; * * ... * * // If need to start Runnable task with executor. * myThreadLocal.set(new MyClass()); // initialize ThreadLocal in 'main' thread * Runnable myRunnable = new Runnable() { * public void run() { * MyClass v = myThreadLocal.get(); // get ThreadLocal in 'child' thread and do something with it * } * } * executor.submit(ThreadLocalPropagateContext.wrap(myRunnable)); // wrap Runnable and submit it to executor * </pre> * * @author andrew00x */ public class ThreadLocalPropagateContext { private static CopyOnWriteArrayList<ThreadLocal<?>> toPropagate = new CopyOnWriteArrayList<>(); /** * Register ThreadLocal in this context. After registration value of ThreadLocal from parent Thread is copied to the child thread when * method {@link #wrap(Runnable)} or {@link #wrap(java.util.concurrent.Callable)}. */ public static void addThreadLocal(ThreadLocal<?> threadLocal) { if (threadLocal == null) { throw new IllegalArgumentException(); } toPropagate.addIfAbsent(threadLocal); } /** * Get list of all registered ThreadLocal. * * @return list of all registered ThreadLocal */ public static ThreadLocal<?>[] getThreadLocals() { return toPropagate.toArray(new ThreadLocal[toPropagate.size()]); } /** * Register ThreadLocal from this context. * * @see #addThreadLocal(ThreadLocal) */ public static void removeThreadLocal(ThreadLocal<?> threadLocal) { if (threadLocal == null) { return; } toPropagate.remove(threadLocal); } /** Clear all registered ThreadLocal variables. */ public void clear() { toPropagate.clear(); } public static Runnable wrap(Runnable task) { return new CopyThreadLocalRunnable(task); } public static <T> Callable<T> wrap(Callable<? extends T> task) { return new CopyThreadLocalCallable<>(task); } static ThreadLocalState currentThreadState() { final ThreadLocal[] threadLocals = toPropagate.toArray(new ThreadLocal[toPropagate.size()]); final Object[] values = new Object[threadLocals.length]; for (int i = 0, l = threadLocals.length; i < l; i++) { values[i] = threadLocals[i].get(); } return new ThreadLocalState(threadLocals, values); } static class ThreadLocalState { private final ThreadLocal[] threadLocals; private final Object[] values; private Object[] previousValues; private ThreadLocalState(ThreadLocal[] threadLocals, Object[] values) { this.threadLocals = threadLocals; this.values = values; } @SuppressWarnings("unchecked") void propagate() { previousValues = new Object[threadLocals.length]; for (int i = 0, l = values.length; i < l; i++) { previousValues[i] = threadLocals[i].get(); threadLocals[i].set(values[i]); } } @SuppressWarnings("unchecked") void cleanup() { if (previousValues == null) { return; // method propagate wasn't called } for (int i = 0, l = previousValues.length; i < l; i++) { threadLocals[i].set(previousValues[i]); } } } private ThreadLocalPropagateContext() { } }