Java-ThreadLocal详解

ThreadLocal使用场景

两大使用场景——ThreadLocal的用途

  • 典型场景1:每个线程需要一个独享的对象(通常是工具类,典
    型需要使用的类有SimpleDateFormat和Random)

  • 典型场景2:每个线程内需要保存全局变量(例如在拦截器中获
    取用户信息),可以让不同方法直接使用,避免参数传递的麻烦

典型场景1:每个线程需要一个独享的对象

1.用两个线程打印日期

Java-ThreadLocal详解_第1张图片

package MyThreadLocal;

import java.text.SimpleDateFormat;
import java.util.Date;

public class ThreadLocal_01 {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                //10秒后的时间
                String date = new ThreadLocal_01().date(10);
                System.out.println(date);
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                //1007秒后的时间
                String date = new ThreadLocal_01().date(10000567);
                System.out.println(date);
            }
        }).start();

    }
    public String date (int seconds){
        //参数的单位是毫秒,从1970.1.1 00:00:00 GMT计时
        Date date = new Date(1000 * seconds);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        return simpleDateFormat.format(date);
    }

}

2.用10个线程打印

Java-ThreadLocal详解_第2张图片

package MyThreadLocal;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 10个线程打印日期
 */

public class ThreadLocal_02 {
    public static void main(String[] args) throws InterruptedException {
        for (int i =0;i<30;i++) {
            int finalI = i;
            new Thread(new Runnable() {
                @Override
                public void run() {
                    //10秒后的时间
                    String date = new ThreadLocal_02().date(finalI);
                    System.out.println(date);
                }
            }).start();
            Thread.sleep(1);
        }
    }
    public String date (int seconds){
        //参数的单位是毫秒,从1970.1.1 00:00:00 GMT计时
        Date date = new Date(1000 * seconds);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        return simpleDateFormat.format(date);
    }

}

这样的写法并不优雅

3.当需求变成1000个,使用线程池

package MyThreadLocal;

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

/**
 * 1000个线程打印日期,用线程池来执行
 */

public class ThreadLocal_03 {
    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() {
                    //10秒后的时间
                    String date = new ThreadLocal_03().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 simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        return simpleDateFormat.format(date);
    }

}

这种方法每次都要新建一个SimpleDateFormat对象

4.优化1000个线程打印日期,用同一个SimpleDateFormat对象

package MyThreadLocal;

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

/**
 * 解决重复重复创建对象,浪费资源
 * 线程不安全,会出现相同时间
 */

public class ThreadLocal_04 {
    public static ExecutorService threadPool = Executors.newFixedThreadPool(10);
    public static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    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() {
                    //10秒后的时间
                    String date = new ThreadLocal_04().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);
        return simpleDateFormat.format(date);
    }

}


5.避免出现重复时间,加锁

package MyThreadLocal;

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

/**
 * 解决重复重复创建对象,浪费资源
 * 线程不安全,会出现相同时间
 * 加锁
 */

public class ThreadLocal_05 {
    public static ExecutorService threadPool = Executors.newFixedThreadPool(10);
    public static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
    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() {
                    //10秒后的时间
                    String date = new ThreadLocal_05().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 simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String s = null;
        //锁
        synchronized (ThreadLocal_04.class){
            s = simpleDateFormat.format(date);
        }
        return s;
    }

}

SimpleDateFormat的进化之路

  1. 2个线程分别用自己的SimpleDateFormat,这没问题
  2. 后来延展出10个
  3. 但是当需求变成1000个,那么必然要用到线程池
  4. 所有线程都公用一个SimpleDateFormat对象
  5. 这是线程不安全的,出现了并发安全问题
  6. 我们可以加锁,加锁后结果正常,但是效率低
  7. 更好的解决方案是使用ThreadLocal

你可能感兴趣的:(后端面试题,java,spring,mvc)