ThreadLocal
是一个线程变量工具类。主要用于将私有线程和该线程存放的副本对象作一个映射,各个线程之间的变量互不干扰,例如A线程和B线程都想使用一个变量,此时就存在强资源的问题,而这个变量他能够有多份,此时就能够用ThreadLocal
做为变量的管理者 ,不存在多线程隐患. 同时他隐式的能够做为一个线程内部的传递信息的一个工具 .java
ThreadLocal
从另外一个角度来解决线程的并发访问。ThreadLocal
会为每个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。例如在多线程中都去使用一个对象,可是又但愿互不干涉,此时就须要用到ThreadLocal
.编程归纳起来讲,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而
ThreadLocal
采用了“以空间换时间”的方式。前者仅提供一份变量,让不一样的线程排队访问,然后者为每个线程都提供了一份变量,所以能够同时访问而互不影响。多线程
咱们能够发现 每个 线程 都有一个 map对象 , map存放的是本地线程对象和副本变量 .而后这个map对象由一个 threadlocal对象 维护 ,他负责去添加和维护并发
因此对于不一样的线程,每次获取副本值时,别的线程并不能获取到当前线程的副本值,造成了副本的隔离,互不干扰。less
ThreadLocal values与这个线程有关,这个map被这个thread所维护ide
public class Thread implements Runnable {
/* ThreadLocal values pertaining(属于) to this thread. This map is maintained(被..维护) * by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
复制代码
1 . public T get() 获取当前线程的副本变量值。工具
2 . public void set(T value) 保存当前线程的副本变量值。this
3 . public void remove() 移除当前前程的副本变量值。spa
4 . public static ThreadLocal withInitial(Supplier supplier) 初始化变量线程
Returns the value in the current thread's copy of this thread-local variable. If the variable has no value for the current thread, it is first initialized to the value returned by an invocation of the initialValue method.
返回当前线程的线程局部变量副本中的值。若是变量没有当前线程的值,则首先将其初始化为调用initialValue方法返回的值。
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 initialValue method to set the values of thread-locals.
将此线程局部变量的当前线程副本设置为指定的值。大多数子类不须要重写这个方法,只须要依赖initialValue方法来设置线程局部变量的值。
Removes the current thread's value for this thread-local variable. If this thread-local variable is subsequently read by the current thread, its value will be reinitialized by invoking its initialValue method, unless its value is set by the current thread in the interim. This may result in multiple invocations of the initialValue method in the current thread.
1 . 删除此线程中的局部变量。若是这个线程局部变量随后又被调用,那么它的值将经过调用其initialValue方法从新初始化,除非它的值是由当前线程在过渡期间设置的。这可能致使在当前线程中屡次调用initialValue方法。
2 . 还要注意一点就是 不remove 会发生内存泄漏,溢出,看你线程消耗吧,固有线程随意了,
Creates a thread local variable. The initial value of the variable is determined by invoking the get method on the Supplier.
创造一个线程的局部变量 , 经过调用get方法来肯定变量的初始值。
public class TestThreadLocal2 {
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
public static Connection getConnection(){
Connection connection = threadLocal.get();
if (null == connection) {
connection = Datasource.get();
threadLocal.set(connection);
}
return connection;
}
public static void remove(){
threadLocal.remove();
}
public static void main(String[] args) throws InterruptedException {
Executor executors = Executors.newFixedThreadPool(10);
executors.execute(()->{
System.out.println(getConnection());
});
executors.execute(()->{
System.out.println(getConnection());
});
System.out.println(getConnection());
}
private static class Connection{
String name;
public Connection(String name) {
this.name = name;
}
}
private static class Datasource{
static ArrayBlockingQueue<Connection> connections = new ArrayBlockingQueue<Connection>(5);
static {
connections.offer(new Connection("1"));
connections.offer(new Connection("2"));
connections.offer(new Connection("3"));
connections.offer(new Connection("4"));
connections.offer(new Connection("5"));
}
static Connection get(){
Connection connection =null;
try {
// 移除并返回头部元素 阻塞操做
connection = connections.take();
// 把移除的元素再添加回去 好比 一开始是 1 2 3 4 5 如今 take一个变成了 2 3 4 5 _ 而后put变成了 2 3 4 5 1 .
connections.put(connection);
} catch (InterruptedException e) {
System.out.println("线程阻塞");
}
return connection;
}
}
}
复制代码
public class MainTest {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(1000);
// 使用 ThreadLocal
Bank bank = new Bank();
IntStream.range(0, 3).forEach(e->{
pool.execute(()->{
bank.deposit(200,"新员工 : "+e);
});
});
pool.shutdown();
}
}
class Bank {
// 初始化帐户余额为 1000
ThreadLocal<Integer> account = ThreadLocal.withInitial(() -> 1000);
public void deposit(int money,String name) {
System.out.println(name + "--当前帐户余额为:" + account.get());
account.set(account.get() + money);
System.out.println(name + "--存入 " + money + " 后帐户余额为:" + account.get());
account.remove();
}
}
复制代码
输出结果 :
新员工 : 0--当前帐户余额为:1000
新员工 : 0--存入 200 后帐户余额为:1200
新员工 : 2--当前帐户余额为:1000
新员工 : 2--存入 200 后帐户余额为:1200
新员工 : 1--当前帐户余额为:1000
新员工 : 1--存入 200 后帐户余额为:1200
复制代码
public class DateUtil {
private static ThreadLocal<SimpleDateFormat> sdf = ThreadLocal
.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
//
public static String format(Date date) {
String msg = null;
try {
// 调用get方法 -> withInitial初始化
msg = sdf.get().format(date);
} finally {
// 用完 remove
sdf.remove();
}
return msg;
}
public static String formatJava8(Date date){
return LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
}
public static void main(String[] args) throws Exception {
System.err.println(format(new Date()));
System.err.println(formatJava8(new Date()));
}
}
复制代码
static class Entry extends WeakReference<ThreadLocal> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal k, Object v) {
// 只有key是弱类型引用,value并非 , 因此容易出现内存溢出的现象,若是不去手动remove的话
super(k);
value = v;
}
.....
}
复制代码
因为ThreadLocalMap的key是弱引用,而Value是强引用。这就致使了一个问题,ThreadLocal在没有外部对象强引用时,发生GC时弱引用Key会被回收,而Value不会回收,若是建立ThreadLocal的线程一直持续运行,那么这个Entry对象中的value就有可能一直得不到回收,发生内存泄露。
如何避免泄漏 既然Key是弱引用,那么咱们要作的事,就是在调用ThreadLocal的get()、set()方法时完成后再调用remove方法,将Entry节点和Map的引用关系移除,这样整个Entry对象在GC Roots分析后就变成不可达了,下次GC的时候就能够被回收。
若是使用ThreadLocal的set方法以后,没有显示的调用remove方法,就有可能发生内存泄露,因此养成良好的编程习惯十分重要,使用完ThreadLocal以后,记得调用remove方法。