ThreadLocal详解

在这里插入图片描述
在这里插入图片描述
第一种应用场景主要解决的是,工具类线程不安全,需要去解决工具类线程不安全的问题。
第二种应用场景主要解决的是,参数传递麻烦的问题。
在这里插入图片描述
下面进行的是SimpleDateFormat进行之路
在这里插入图片描述

package threadlocalpratice;

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

public class ThreadLocalNormalUsage00 {
    public String data(int second){
        //参数的单位是毫秒,从1970-01-01 00:00:00开始
        Date date = new Date(1000 * second);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        return simpleDateFormat.format(date);
    }

    public static void main(String[] args) {
       new Thread(new Runnable() {
           @Override
           public void run() {
               String data = new ThreadLocalNormalUsage00().data(100000);
               System.out.println(data);
           }
       }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                String data = new ThreadLocalNormalUsage00().data(100);
                System.out.println(data);
            }
        }).start();
    }
}

这是两个线程用SimpleDateFormat

package threadlocalpratice;

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

/**
 * 1000个打印日期的任务,用线程池来执行
 */
public class ThreadLocalNormalUsage01 {
    public static ExecutorService threadPool =  Executors.newFixedThreadPool(10);
    public String data(int second){
        //参数的单位是毫秒,从1970-01-01 00:00:00开始
        Date date = new Date(1000 * second);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        return simpleDateFormat.format(date);
    }

    public static void main(String[] args) {

        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            //将任务提交到线程池中
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    String data = new ThreadLocalNormalUsage01().data(1000 + finalI);
                    System.out.println(data);
                }
            });
        }
        threadPool.shutdown();

    }
}

这是1000个线程,使用线程池的方法来提高效率。这里的话发现SimpleDateFormat工具类,在每个线程中都被创建出来,浪费资源。

package threadlocalpratice;

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

/**
 * 1000个打印日期的任务,用线程池来执行
 * 由于1000个线程共用一个simpleDateFormat对象,会浪费,
 * 所以把它写在类对象上。让1000个线程共用一个SimpleDateFormat对象
 * 发现共用一个SimpleDateFormat会导致线程不安全
 */
public class ThreadLocalNormalUsage02 {
    public static ExecutorService threadPool =  Executors.newFixedThreadPool(10);
    static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

    public String data(int second){
        //参数的单位是毫秒,从1970-01-01 00:00:00开始
        Date date = new Date(1000 * second);
        return simpleDateFormat.format(date);
    }

    public static void main(String[] args) {

        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            //将任务提交到线程池中
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    String data = new ThreadLocalNormalUsage02().data(1000 + finalI);
                    System.out.println(data);
                }
            });
        }
        threadPool.shutdown();
    }
}

这里让1000个线程都共用一个SimpleDateFormat对象。

package threadlocalpratice;

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

/**
 * 1000个打印日期的任务,用线程池来执行
 * 用加锁的方法来保证线程安全
 */
public class ThreadLocalNormalUsage03 {
    public static ExecutorService threadPool = Executors.newFixedThreadPool(10);
    static SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");

    public String data(int second) {
        //参数的单位是毫秒,从1970-01-01 00:00:00开始
        Date date = new Date(1000 * second);
        String s = null;
        //选取simpleDateFormat来作为同步监控器
        synchronized (simpleDateFormat) {
           s = simpleDateFormat.format(date);
        }
        return s;
    }

    public static void main(String[] args) {

        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            //将任务提交到线程池中
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    String data = new ThreadLocalNormalUsage03().data(1000 + finalI);
                    System.out.println(data);
                }
            });
        }
        threadPool.shutdown();
    }
}

上面代码用加锁的方法来解决线程不安全的问题。但是,发现加锁的时候,效率比较低了。
在这里插入图片描述
给每一个线程都添加ThreadLocal 就是复制一份副本。

package threadlocalpratice;

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

/**
 * 1000个打印日期的任务,用线程池来执行
 * 由于1000个线程共用一个simpleDateFormat对象,会浪费,
 * 使用ThreadLocal方法,由于只有10个线程,所以,其实只要同一个simpleDateFormat对象
 * 复制9份就可以了
 *
 */
public class ThreadLocalNormalUsage05 {
    public static ExecutorService threadPool =  Executors.newFixedThreadPool(10);
     ThreadLocal<SimpleDateFormat> simpleDateFormat = new ThreadLocal(){
        @Override
        protected SimpleDateFormat initialValue(){
            return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        }
    };

    public String data(int second){
        //参数的单位是毫秒,从1970-01-01 00:00:00开始
        Date date = new Date(1000 * second);
        return simpleDateFormat.get().format(date);
    }

    public static void main(String[] args) throws InterruptedException {
        HashSet set = new HashSet();
        for (int i = 0; i < 1000; i++) {
            int finalI = i;
            //将任务提交到线程池中
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    String data = new ThreadLocalNormalUsage05().data(1000 + finalI);
                    System.out.println(data);
                    set.add(data);
                }
            });
        }
        threadPool.shutdown();
        Thread.sleep(2000);
        System.out.println(threadPool.isTerminated());
        System.out.println(set.size());
    }
}

在这里插入图片描述
ThreadLocal 用途二:
在这里插入图片描述
在这里插入图片描述
传参的过程过于麻烦。不合适。
目标:每个线程内需要保存全局变量,可以让不同方法直接使用,避免参数传递的麻烦。
在这里插入图片描述
假设用UserMap
在这里插入图片描述
但是,这个方法也有问题:
当多线程同时工作时,我们需要保证线程安全,可以用synchronized,也可以用ConcurrentHashMap,但是,无论使用什么,都会对性能有所影响。
用ThreadLocal 不会影响性能
在这里插入图片描述
在这里插入图片描述

package threadlocalpratice;


import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 演示ThreadLocal的应用场景2,进行数据的传输
 */
public class ThreadLocalUsage06 {
    public static void main(String[] args) {
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 100; i++) {

            int finalI = i;
            threadPool.submit(new Runnable() {
                @Override
                public void run() {
                    new Service1( " " + finalI).process();
                }
            });
        }
        threadPool.shutdown();

    }
}

class Service1 {
    User user;
    public Service1(String name){
         user = new User(name);
    }

    public void process() {

        //对对象中的数据进行设置
        UserContextHolder.holder.set(user);
        new Service2().process();
        new Service3().process();
    }
}

class Service2{
    public void process(){
        //这时候就可以得到User了
        User user = UserContextHolder.holder.get();
        System.out.println( user.name +" Service2拿到的user " + user.name);
    }
}
class Service3{
    public void process(){
        //这时候就可以得到User了
        User user = UserContextHolder.holder.get();
        System.out.println( user.name + " Service3拿到的user " + user.name);
    }
}
class UserContextHolder {
    //使用方式2时,不需要进行初始化
    public static ThreadLocal<User> holder = new ThreadLocal<>();
}

class User {
    String name;

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

总结
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
ThreadLocal的好处:
在这里插入图片描述
在这里插入图片描述
源码分析:
在这里插入图片描述
在这里插入图片描述
每一个Thread线程中,都会有一个ThreadLocalMap类, 里面会存储很多ThreadLocal 为什么需要map因为一个线程中可能会有多个ThreadLocal。
ThreadLocal:方法
在这里插入图片描述
下面在源码中进行查看,再没重写之前默认返回为null,重写后,调用get方法时就会执行该方法。
在这里插入图片描述
在这里插入图片描述
如果第一次调用get方法时,就会调用InitialValue。如果,之前调用了set方法,那么就不会去调用initialValue方法了。
原因如下:
在这里插入图片描述
由于map中不为null 因为你给它设置了值,所以,它就不会执行setInitialValue方法了。而拿到的result是set设置进行的。
在这里插入图片描述
在这里插入图片描述

分析get方法:
在这里插入图片描述
t代表当前线程:
在这里插入图片描述
getMap返回的是当前线程的threadLocals,这个threadLocals代表的是每个线程都有的ThreadLocalMap
在这里插入图片描述
如果map为空,就是没有set过 那么就是执行setInitialValue方法,这个方法代表的就是初始化InitialValue。如果map不为空(已经初始化过或者set过)。注意,这个map以及map中的key和value都是保存在线程中的,而不是保存在ThreadLocal中。
如果不为null 就以当前的ThreadLocal作为key(this)去找value,然后来进行返回。
下面分析的是set方法
在这里插入图片描述
相比之下,set就简单得多了:如果map不为空,那么就直接加入。如果为空,就新建,新建如下:在这里插入图片描述
remove:方法
在这里插入图片描述
这里先获取ThreadLocal中的ThreadLocalMap 如果这个map不为空,就进行删除,但是,它不会把所有的都删除,它会把删除this,这个this代表的是调用它的ThreadLocal这个key所代表的key-value键值对。只能删除一个。
在这里插入图片描述
ThreadLocalMap类是Thread里面的一个成员变量。threadLocals ,类型是ThreadLocalMap
在这里插入图片描述
其有一个属性是:
在这里插入图片描述
这个可以认为是一个map,键值对:
键:ThreadLocal
值:实际需要的成员变量,比如uesr或者simpleDateFormat对象
但是,它和map有一点不同,hashmap碰到冲突会用链表的方法,或者红黑树,而它是进行找空的值,而进行填入。
总结一下
在这里插入图片描述
在每一个Thread中,都是会有一个ThreadLocalMap,里面会有一个个键值对。存入的key是ThreadLocal 类型,值的话,所有类型都可以。但调用get时,如果在ThreadLocalMap中没有,就会根据初始化函数进行创建。
ThreadLocal注意点
1.内存泄漏
在这里插入图片描述
在这里插入图片描述
这个key ThreadLocal代表的是弱引用。
而 value = v 代表的就是强引用;
在这里插入图片描述
但是,
在这里插入图片描述
因为value和Thread之间是强引用。所以,比如说我创建了十个对象,可以已经不用了,但是,还在内存中。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
就是说,随着时间的增加,弱引用会被垃圾回收栈回收,但是,强引用会一直都在。这个value会一直在内存中。
在这里插入图片描述
在线程关闭之前要进行线程回收。不在使用的时候,主动调用remove()方法
在这里插入图片描述
ThreadLocal 碰到的空指针问题
package threadlocalpratice;

public class ThreadLocalNPE {
ThreadLocal longThreadLocal = new ThreadLocal<>();
public void set(){
longThreadLocal.set(Thread.currentThread().getId());
}
public long get(){
return longThreadLocal.get();
}

public static void main(String[] args) {
    ThreadLocalNPE threadLocalNPE = new ThreadLocalNPE();
    //这行语句会导致空指针异常,理论上,ThreadLocalNPE.longThreadLocal.get返回的是null。不会返回空指针异常的
    System.out.println(threadLocalNPE.get());

    Thread thread = new Thread(
            new Runnable() {
                @Override
                public void run() {
                    threadLocalNPE.set();
                    long l = threadLocalNPE.get();
                    System.out.println(l);
                }
            }
    );
    //子线程的id
    thread.start();
}

}
这里会导致空指针异常:
原因如下:
在这里插入图片描述
longThreadLocal.get()返回的是null,是Long类型的,但是想给他转化为long类型,所以,才导致了空指针类型报错。解决措施:只要把long改成Long即可,就不会有装箱拆箱问题了。
注意点:
在这里插入图片描述
在这里插入图片描述

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值