PS:
关于多线程共享变量ThreadLocal<T>
变量值的共享可以使用public static 变量的形式,所有的线程都可以使用同一个public static变量。如果想实现每一个线程都有自己的共享变量,如何实现?
JDK中提供了类ThreadLocal<T>可以解决这样的问题。原理:
ThreadLocal所属包java.lang.ThreadLocal<T> 是个泛型class
ThreadLocal为解决多线程并发问题提供了新的思路,使用这个类库可以简洁的实现多线程程序,
当使用ThreadLocal维护变量时,ThreadLocal为每一个使用该变量的线程提供独立的变量副本,所以没一个线程都可以改变自己的副本,而不会影响到其他线程对于的变量副本。
从线程的角度看,目标变量就象是线程的本地变量,这也是类名中“Local”所要表达的意思。
ThreadLocal为每一个线程维护变量的副本的原理:在ThreadLocal类中有一个Map集合,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值对应线程的变量副本,查看ThreadLocal的源码可以看到
其开放的方法主要有:
public T get() 返回当前线程所对应的线程局部变量。
public void set(T value) 设置当前线程的线程局部变量的值。
public void remove() 将当前线程局部变量的值删除,目的是为了减少内存的占用,注(当线程结束后,对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度)
public static <S> ThreadLocal<S> withInitial(Supplier<? extends S> supplier) 初始化值
在创建变量设置值的时候set(T value) ,可以看到使用到了ThreadLocalMap类,该类是ThreadLocal中的一个静态内部类
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap用于存储每一个线程的变量副本:
/**
* ThreadLocalMap is a customized hash map suitable only for
* maintaining thread local values. No operations are exported
* outside of the ThreadLocal class. The class is package private to
* allow declaration of fields in class Thread. To help deal with
* very large and long-lived usages, the hash table entries use
* WeakReferences for keys. However, since reference queues are not
* used, stale entries are guaranteed to be removed only when
* the table starts running out of space.
*/
static class ThreadLocalMap {
/**
* The entries in this hash map extend WeakReference, using
* its main ref field as the key (which is always a
* ThreadLocal object). Note that null keys (i.e. entry.get()
* == null) mean that the key is no longer referenced, so the
* entry can be expunged from table. Such entries are referred to
* as "stale entries" in the code that follows.
*/
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
...省略....
}
示例:
1、创建两个线程实现变量存储
public class ThreadA extends Thread {
public void run() {
// 给当前线程设置变量值
ThreadLocalTool.THREAD_LOCAL.set("threadA:[" + Math.random() + "]");
// 输出
System.out.println("get threadA:" + ThreadLocalTool.THREAD_LOCAL.get());
}
}
public class ThreadB extends Thread {
public void run() {
// 给当前线程设置变量值
ThreadLocalTool.THREAD_LOCAL.set("threadB:[" + Math.random() + "]");
// 输出
System.out.println("get threadB:" + ThreadLocalTool.THREAD_LOCAL.get());
}
}
public class Client {
public static void main(String[] args) {
ThreadA tA = new ThreadA();
ThreadB tB = new ThreadB();
// 给当前线程设置变量值
ThreadLocalTool.THREAD_LOCAL.set("main:[" + Math.random() + "]");
tA.start();
tB.start();
System.out.println("get main:" + ThreadLocalTool.THREAD_LOCAL.get());
}
}
以上示例运行之后,输出:
get main:main:[0.7622350866507818]
get threadB:threadB:[0.6860560404190107]
get threadA:threadA:[0.22195491171797677]
可以看出Threadlocal类解决的是变量在不同线程间的隔离性,也就是不同线程用自己的值,不同线程的值是可以放入Threadlocal中进行保存的。
Threadlocal的优势
ThreadLocal和线程同步机制相比有什么优势呢?ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。
1)在同步机制中,通过对象的锁机制保证同一时间只有一个线程访问变量。这时该变量是多个线程共享的,使用同步机制要求程序慎密地分析什么时候对变量进行读写,什么时候需要锁定某个对象,什么时候释放对象锁等繁杂的问题,程序设计和编写难度相对较大。
2)而ThreadLocal则从另一个角度来解决多线程的并发访问。ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。
3)概括起来说,对于多线程资源共享的问题,同步机制采用了"以时间换空间"的方式,而ThreadLocal采用了"以空间换时间"的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。