ThreadLocal的理解与正确使用

本地变量副本

别人总是说用使用ThreadLocal可以避免线程不安全的问题,因为ThreadLocal使用的本地变量副本。怎么理解本地变量副本这个概念呢?

  1. 线程的创建和销毁是非常消耗资源的,所以有了线程池,重复利用线程
  2. 一个线程,每执行一个任务,任务中都去创建对象,例如SimpleDateFormat对象,同样很消耗资源
  3. 使用ThreadLocal给当前线程绑定一个SimpleDateFormat对象,让这个线程每次执行任务,通过ThreadLocal#get方法获取SimpleDateFormat对象,代替每个任务执行中创建SimpleDateFormat对象
  4. 注意:如果使用线程池做SimpleDateFormat测试,在任务中直接输出SimpleDateFormat对象的地址,你会发现对象地址总是同一个,无论是正例还是反例。这是因为SimpleDateFormat对象的hashCode方法重写了,输出的是表达式的hashCode。如果你要输出SimpleDateFormat的真实的hashCode可以使用System.identityHashCode(obj)方法,在下面案例中有用到

总结:ThreadLocal给当前线程绑定一个对象,当前线程在执行不同的任务时,通过ThreadLocal#get获取当前线程绑定的对象。

正例:

public static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>() {
    
    
    @Override
    protected SimpleDateFormat initialValue() {
    
    
        return new SimpleDateFormat("mm:ss");
    }
};

反例:

public static SimpleDateFormat sdf = new SimpleDateFormat("mm:ss");
public static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>() {
    
    
    @Override
    protected SimpleDateFormat initialValue() {
    
    
        return sdf;
    }
};

SimpleDateFormat案例

正例:

public static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
public static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>() {
    
    
    @Override
    protected SimpleDateFormat initialValue() {
    
    
        return new SimpleDateFormat("mm:ss");
    }
};

public static void main(String[] args) {
    
    
    for (int i = 0; i < 10; i++) {
    
    
        fixedThreadPool.execute(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                SimpleDateFormat simpleDateFormat = threadLocal.get();
                String dateString = simpleDateFormat.format(new Date());
                try {
    
    
                    Date parseDate = simpleDateFormat.parse(dateString);
                    String dateString2 = simpleDateFormat.format(parseDate);
                    System.out.println(dateString.equals(dateString2));
                } catch (ParseException e) {
    
    
                    e.printStackTrace();
                }
                System.out.println(String.format("current thread %s fetch SimpleDateFormat identityHashCode %s SimpleDateFormat addr %s", Thread.currentThread().getName(), System.identityHashCode(simpleDateFormat), Integer.toHexString(System.identityHashCode(simpleDateFormat))));
            }
        });
    }
    fixedThreadPool.shutdown();
}

反例:

public static ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
public static SimpleDateFormat staticSimpleDateFormat = new SimpleDateFormat("mm:ss");
public static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>() {
    
    
    @Override
    protected SimpleDateFormat initialValue() {
    
    
        return staticSimpleDateFormat;
    }
};

public static void main(String[] args) throws Exception {
    
    
    for (int i = 0; i < 10; i++) {
    
    
        fixedThreadPool.execute(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                SimpleDateFormat simpleDateFormat = threadLocal.get();
                String dateString = simpleDateFormat.format(new Date());
                try {
    
    
                    Date parseDate = simpleDateFormat.parse(dateString);
                    String dateString2 = simpleDateFormat.format(parseDate);
                    System.out.println(dateString.equals(dateString2));
                } catch (ParseException e) {
    
    
                    e.printStackTrace();
                }
            }
        });
    }
    fixedThreadPool.shutdown();
}

Guess you like

Origin blog.csdn.net/qq_30038111/article/details/112445854