最后的结果可以发现,会出现相同的值,这说明发生了线程安全问题。因为所有线程公用了一个SimpleDateFormat对象,当发生安全问题时,导致拿到的值相同 这时候可以考虑加锁:
synchronized (ThreadLocalNormalUsage04.class) { s = dateFormat.format(date); }然而加锁的效率太低。
这时候就可以考虑使用ThreadLocal,利用ThreadLocal,给每个线程分配自己的dateFormat对象,既保证线程安全,又高效利用内存
public class ThreadLocalNormalUsage05 { public String date(int seconds) { //参数的单位是毫秒,从1970.1.1 00:00:00 GMT计时 Date date = new Date(1000 * seconds); //通过threadLocal的get方法获取到SimpleDateFormat对象 SimpleDateFormat dateFormat = ThreadSafeFormatter.dateFormatThreadLocal.get(); return dateFormat.format(date); } public static ExecutorService threadPool = Executors.newFixedThreadPool(10); public static void main(String[] args) throws InterruptedException { for (int i = 0; i < 1000; i++) { int finalI = i; threadPool.submit(new Runnable() { @Override public void run() { String date = new ThreadLocalNormalUsage05().date(10 + finalI); System.out.println(date); } }); } threadPool.shutdown(); } } class ThreadSafeFormatter { public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() { @Override //初始化方法一,重写initialValue protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); } }; //方法二:利用lambda表达式 public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal2 = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss")); }通过ThreadLocal保存全局变量,可以避免传参的麻烦
每个线程存在一个ThreadLocalMap,每个Map里面包含多个ThreadLocal,而每个ThreadLocal都是以K-V形式存储的
该方法会返回当前线程对应的"初始值",这是一个延迟加载的方法,只有在调用get的时候,才会触发。
当线程第一次使用get方法访问变量时,将调用此方法,除非线程先调用了set方法,在这种情况下,不会为线程调用本initialValue方法。
通常每个线程最多调用一次此方法,但如果已经调用了remove()后,在调用get(),则可以再次调用此方法。(remove之后,map变为null)
如果不重写本方法,这个方法会返回null。一般使用匿名内部类的方法来重写initialValue()方法,以便在后续使用中可以初始化副本对象。
//初始化方法一,重写initialValue public static ThreadLocal<SimpleDateFormat> dateFormatThreadLocal = new ThreadLocal<SimpleDateFormat>() { @Override protected SimpleDateFormat initialValue() { return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"); } };ThreadLocal就存在内存泄漏的问题 在上面的ThreadLocalMap里面,最重要的就是Entry对象,它包含key-value,可以看到整个Entry对象继承WeakReference是弱引用(当某个对象只被弱引用关联,它就可以被回收),然而,value=v属于强引用
正常情况下,当线程停止,它里面的value就会被垃圾回收,那么就不存在强引用了但是当线程不终止,那么key就不能被回收,就存在 Thread——>ThreadLocalMap——>Entry(key为null)——>ValueJDK已经考虑到了这样的问题,在set,remove,rehash这些方法中会扫描key为null的Entry,并把value设置为null,这样就可以被回收 但是,若这些方法都没有被调用,线程又不停止,那么就会出现内存泄漏 在使用完ThreadLocal之后(业务逻辑中不需要再使用),主动调用remove()方法,避免内存泄漏。
复习重点:initialize()、get()、内存泄漏
