ThreadLocal从入门到精通--彻底掌握

本文带领你从各个方面了解并掌握ThreadLocal,进而彻底精通。

使用场景

  1.每个线程需要一个独享的对象(不安全的工具类)

  2.每个线程需要保存全局变量(可让不同的方法调用)

  以下实例代码对共享的操作添加了类锁,会有性能问题,见下图

  

package threadPool;

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

/**
 * 测试不使用ThreadLocal时如何对线程的共享变量加锁
 */
public class NotUseThreadLocalTest01 {
    SimpleDateFormat simpleDateFormat=new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    public static void main(String[] args) {
        //创建线程池去执行1000个任务
        ExecutorService executorService= Executors.newFixedThreadPool(10);
        NotUseThreadLocalTest01 threadLocalTest01=new NotUseThreadLocalTest01();
        for (int i=0;i<1000;i++){
            int finalI = i;
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    //获取当前时间戳
                    System.out.println(threadLocalTest01.getDateStrFromSeconds(finalI));
                }
            });
        }
        executorService.shutdown();


    }

    public String getDateStrFromSeconds(int seconds){
        Date date=new Date(seconds*1000);
        String dateStr=null;
        //加类锁
        synchronized (NotUseThreadLocalTest01.class){
            dateStr=simpleDateFormat.format(date);
        }
        return dateStr;
    }
}

  以下实例代码我们使用threadlocal去生成dateformat,见下文。

   

package threadPool;

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

/**
 * 测试ThreadLocal时如何对线程的共享变量加锁
 */
public class UseThreadLocalTest02 {
    public static void main(String[] args) {
        //创建线程池去执行1000个任务
        ExecutorService executorService= Executors.newFixedThreadPool(10);
        UseThreadLocalTest02 threadLocalTest01=new UseThreadLocalTest02();
        for (int i=0;i<1000;i++){
            int finalI = i;
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    //获取当前时间戳
                    System.out.println(threadLocalTest01.getDateStrFromSeconds(finalI));
                }
            });
        }
        executorService.shutdown();


    }

    public String getDateStrFromSeconds(int seconds){
        Date date=new Date(seconds*1000);
        return DateFormatter.simpleDateFormatThreadLocal.get().format(date);
    }
}

class DateFormatter{
    public static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocal=new ThreadLocal<SimpleDateFormat>(){
        @Override
        protected SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        }
    };
    //也可以使用lamda初始化
    public static ThreadLocal<SimpleDateFormat> simpleDateFormatThreadLocalWithLamda=ThreadLocal.withInitial(()->
        new SimpleDateFormat("yyyy-MM-dd hh:mm:ss")
    );
}

  以下实现,我们对线程中不同的方法基于threadlocal传递参数,见下图

  

package threadPool;

/**
 * 实现主线程,基于threadlocal传递参数
 */
public class UserThreadLocal03 {

    public static void main(String[] args) {
        try{
            UserThreadLocal03 userThreadLocal03=new UserThreadLocal03();
            userThreadLocal03.serviceOne();
        }finally {
             ThreadLocalParams.userThreadLocal.remove();
        }

    }

    public void  serviceOne(){
        ThreadLocalParams.userThreadLocal.set(new User("123"));
        serviceTwo();
    }
    public void serviceTwo(){
        System.out.println("userId:"+ThreadLocalParams.userThreadLocal.get().getId());
        serviceThree();
    }
    public void serviceThree(){
        System.out.println("userId:"+ThreadLocalParams.userThreadLocal.get().getId());
    }

}

class User
{
    private String id;

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

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }
}

class ThreadLocalParams{
    public static ThreadLocal<User> userThreadLocal=new ThreadLocal<User>();
}

 ThreadLocal方法介绍

  1.initialValue:初始化

  2.get 获取当前线程threadlocal对应的值

  3.set 设置当前线程threadLocal的值

  4.remove移除

 ThreadLocal部分源码分析

  主要分析initialValue、get、set等

  get方法如下

  

  public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

  get方法首先会从当前threadlocalMap中获取,如果有了,直接返回,若没有的时候,则会调用initialValue方法后返回,则说明initialValue这个方法是延迟加载的,只有在用的时候才会加载,接下面我们查看下set方法的部分实现。

  set方法的实现如下图所示:

  

  public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

  从上面代码中我们发现set方法最重要的调用方法为map.set,将线程变量的值进行写入,接下面我们查看下initialValue方法,

  initialValue方法部分实现如下图所示:

  

  private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

  我们发现initialValue方法最终调用的也是map.set方法,进而可知,initialValue和set的实现有异曲同工之妙。

ThreadLocal内存泄漏问题

  我们查看remove代码可以看到下面这样的实现,会执行clear操作,所有在线程池中传递变量后,不再使用时,需要显示执行remove等类似方法进行清除。

  

 private void remove(ThreadLocal<?> key) {
            Entry[] tab = table;
            int len = tab.length;
            int i = key.threadLocalHashCode & (len-1);
            for (Entry e = tab[i];
                 e != null;
                 e = tab[i = nextIndex(i, len)]) {
                if (e.get() == key) {
                    e.clear();
                    expungeStaleEntry(i);
                    return;
                }
            }
        }

.............

 public void clear() {
        this.referent = null;
    }


.............

private T referent;         /* Treated specially by GC */

  ThreadLocal优点

  1.解决线程安全问题

  2.减少内存的使用

  3.避免或者减少线程方法中的方法传递

  ThreadLocal空指针异常

    详细介绍空指针异常。threadLocal本身没有空指针异常, 多数情况是类型进行自动装箱时导致的,可以看下实例代码,如下所示:

    

package threadPool;

public class ThreadLocalNullPointExceptionTest {
    private ThreadLocal<Long> threadLocal= new ThreadLocal<Long>();
    public void set(){
        threadLocal.set(Thread.currentThread().getId());
    }

    /**
     * 空的null转成long的时候会出现空指针异常
     * Exception in thread "main" java.lang.NullPointerException
     * 	at threadPool.ThreadLocalNullPointExceptionTest.get(ThreadLocalNullPointExceptionTest.java:9)
     * 	at threadPool.ThreadLocalNullPointExceptionTest.main(ThreadLocalNullPointExceptionTest.java:14)
     * @return
     */
    public long get(){
        return threadLocal.get();
    }

    public static void main(String[] args) {
        ThreadLocalNullPointExceptionTest threadLocalNullPointExceptionTest=new ThreadLocalNullPointExceptionTest();
        System.out.println(threadLocalNullPointExceptionTest.get());
    }
}

  

原文地址:https://www.cnblogs.com/cnxieyang/p/12745163.html