Java并发编程-关于ThreadLocal那些事

第一版

首先看一个SimpleDateFormat的例子

    public static void main(String[] args) {
    new Thread(()->{
        String date=new ThreadLocalDemo2().formatDate(1);
        System.out.println(date);
    }).start();
    new Thread(()->{
        String date=new ThreadLocalDemo2().formatDate(2);
        System.out.println(date);
    }).start();
    }
   public String formatDate(int sec){
        Date date=new Date(sec*1000);
        SimpleDateFormat simpleDateFormat=new SimpleDateFormat("mm:ss");
        return simpleDateFormat.format(date);
    }

上面的例子显示的是开辟两个线程,输出当前时间,由于都是新建SimpleDateFormat对象,而且线程数比较少,并没有什么竞争,所以答案就是00:01和00:02,并没有冲突发生。

再升级

接着进化一下,用线程池开辟16个线程,然后打印20次

public class ThreadLocalDemo3 implements Runnable {
    public static void main(String[] args) {
        ExecutorService executorService= Executors.newFixedThreadPool(16);
        for (int i = 0; i <20 ; i++) {
            final int finalI=i;
            executorService.submit(()->{
                    String date=new ThreadLocalDemo3().formatDate(finalI);
                    System.out.println(date);
            });
        }
    }
    @Override
    public void run() {

    }
    public String formatDate(int sec){
        Date date=new Date(sec*1000);
        SimpleDateFormat simpleDateFormat=new SimpleDateFormat("mm:ss");
        return simpleDateFormat.format(date);
    }
}

可以看到如下结果

00:05
00:04
00:09
00:02
00:18
00:19
00:17
00:16
00:01
00:08
00:06
00:00
00:10
00:07
00:03
00:11
00:12
00:13
00:14
00:15

这样也是可行的,并没有发生重复的数据,线程是安全的。

再进一步去实现一下

当每次调用都会产生一个新的对象的时候,势必对内存造成更多的占用,现在将SimpleDateFormat实现资源共用。
由于产生了资源共用,则触发了线程不安全的问题,所以导致了重复的结果出现。

尝试解决

可以对SimpleDateFormat进行加锁,使用synchronized修饰

    public String formatDate(int sec) {
        String s=null;
        synchronized (ThreadLocalDemo3.class) {
            Date date = new Date(sec * 1000);
           s= simpleDateFormat.format(date);
        }
        return s;
    }

既然加了锁,当然线程变成安全的了。既然加了锁,那就变成了一种排队的情况,这样处理就会大大降低,所以,可以引入ThreadLocal进行解决。


public class ThreadLocalDemo3 {
    SimpleDateFormat simpleDateFormat = new SimpleDateFormat("mm:ss");
    static ExecutorService executorService = Executors.newFixedThreadPool(8);

    public static void main(String[] args) {

        Set<String> dateSet = new HashSet<>();
        for (int i = 0; i < 1000; i++) {
            final int finalI = i;
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    String date = new ThreadLocalDemo3().getDate(finalI);
                    System.out.println(date);
                    if (dateSet.contains(date)) {
                        throw new RuntimeException("该date已存在");
                    } else {
                        dateSet.add(date);
                    }
                }
            });
        }
        executorService.shutdown();
    }

    public String formatDate(int sec) {
        String s = null;
        synchronized (ThreadLocalDemo3.class) {
            Date date = new Date(sec * 1000);
            s = simpleDateFormat.format(date);
        }
        return s;
    }

    public String getDate(Integer sec) {

        return getThreadLocalSimpleDateFormat().format(new Date(sec*1000));
    }

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

原理是利用ThreadLocal线程进行初始化对象,即每个线程都会创建个SimpleDateFormat对象,对象数并不会太多,有多少线程就会有多少对象,也就是说线程池中有16个线程协助运转,那么就有16个对象。

在ThreadLocal中,线程内的资源是共享的,但是兄弟线程是不共线资源的。
为了防止内存泄漏,使用ThreadLocal后要对其对象进行remove操作

你可能感兴趣的:(Java基础,java,多线程,jvm,并发编程)