ThreadLocal

一.ThreadLocal简介

多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。ThreadLocal是除了加锁这种同步方式之外的一种保证一种规避多线程访问出现线程不安全的方法,加锁会有性能问题,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全问题。

二.ThreadLocal的使用场景

1.第一种场景 重写initialValue   

 initialValue  不重写的情况下是null ,调用get方法时才会去执行initialValue  ,每个线程最多调用一次,如果调用了remove再调用get方法则可以再次调用initialValue  

利用ThreadLocal,给每个线程分配自己的dateFormat对象,保证了线程安全,高效利用内存
package threadlocal;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 描述:     利用ThreadLocal,给每个线程分配自己的dateFormat对象,保证了线程安全,高效利用内存
 */
public class ThreadLocalNormalUsage {

    public static ExecutorService threadPool = Executors.newFixedThreadPool(10);

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 5; i++) {
            int finalI = i;
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    String date = new ThreadLocalNormalUsage().date(finalI);
                    System.out.println(date);
                }
            });
        }
        threadPool.shutdown();
    }

    public String date(int seconds) {
        //参数的单位是毫秒,从1970.1.1 00:00:00 GMT计时
        Date date = new Date(1000 * seconds);
//        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        SimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatThreadLocal2.get();
        System.out.println(
                Thread.currentThread().getName() + ThreadSafeFormatter.dateFormatThreadLocal);
        //通过get方法取出ThreadLocal中的SimpleDateFormat
        SimpleDateFormat simpleDateFormat = ThreadSafeFormatter.dateFormatThreadLocal.get();
        System.out.println(Thread.currentThread().getName() + simpleDateFormat.toString());
        System.out.println(
                Thread.currentThread().getName() + System.identityHashCode(simpleDateFormat));

        return dateFormat.format(date);
    }
}

class ThreadSafeFormatter {

    public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };
    //使用lambda方式重写initialValue方法
    public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal2 = ThreadLocal
            .withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}

2.第二种场景 手动调用set方法  避免传递参数的麻烦

package threadlocal;

/**
 * 描述:     演示ThreadLocal用法2:避免传递参数的麻烦
 */
public class ThreadLocalNormalUsage06 {

    public static void main(String[] args) {
        new Service1().process("");

    }
}

class Service1 {

    public void process(String name) {
        User user = new User("超哥");
        UserContextHolder.holder.set(user);
        new Service2().process();
    }
}

class Service2 {

    public void process() {
        User user = UserContextHolder.holder.get();
        ThreadSafeFormatter.dateFormatThreadLocal.get();
        System.out.println("Service2拿到用户名:" + user.name);
        new Service3().process();
    }
}

class Service3 {

    public void process() {
        User user = UserContextHolder.holder.get();
        System.out.println("Service3拿到用户名:" + user.name);
        UserContextHolder.holder.remove();
    }
}

class UserContextHolder {

    public static ThreadLocal<User> holder = new ThreadLocal<>();

}

class User {

    String name;

    public User(String name) {
        this.name = name;
    }
}

 

三.Thread、ThreadLocal以及ThreadLocalMap三者之间的关系

每个Thread线程内部有一个名为threadLocals的成员变量,该变量的类型为ThreadLocal.ThreadLocalMap类型(类似于一个HashMap)

ThreadLocalMap可以存放多个ThreadLocal,因为一个线程中可能有多个ThreadLocal

其中的key为当前定义的ThreadLocal变量的this引用,value为我们使用set方法设置的值

 

 

四.注意点

ThreadLocalMap中key是弱引用value是强引用,因为线程可能长时间运行,key被GC回收之后,value和线程之间还存在强引用链路,导致无法回收,可能出现OOM

因为一个ThreadLocal不被使用就可能导致value的内存泄漏,所以使用完之后应该主动调用remove方法删除对应的value,避免内存泄漏

 

强引用
我们平日里面的用到的new了一个对象就是强引用,例如 Object obj = new Object();
当JVM的内存空间不足时,宁愿抛出OutOfMemoryError使得程序异常终止也不愿意回收具有强引用
的存活着的对象。
 
弱引用
弱引用是通过WeakReference类实现的,在GC的时候,不管内存空间足不足都会回收这个对象,
适用于内存敏感的缓存,ThreadLocal中的key就用到了弱引用,有利于内存回收。

 

posted @ 2021-11-04 00:07  漩涡·海绵宝宝  阅读(39)  评论(0)    收藏  举报