线程安全的解决方案(ThreadLocal详解)

Posted 秃头小宝儿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程安全的解决方案(ThreadLocal详解)相关的知识,希望对你有一定的参考价值。

1.线程安全的解决方案

  • 1.加锁(synchronized 、Lock:加锁可以解决线程安全的问题,但因为排队处理,所以会带来一定的性能消耗)
    在这里插入图片描述
  • 2.设置私有变量

有没有一种方案既可以避免加锁排队执行,又不会每次执行任务都需要重新创建私有变量呢?

答:有。ThreadLocal线程的本地变量,每个线程创建一个私有变量(以1000个任务10个线程池的实例来说,使用ThreadLocal就是创建10个SimpleDateFormat对象)。

(1)选择ThreadLocal还是锁?

  • 就要看创建实例对象之后的复用率,复用率高就使用ThreadLocal。
  • 如:实现1000个任务的时间格式化。–>线程池、SimpleDateFormat(全局)—>线程不安全的问题

(2)ThreadLocal的使用方法

  • 1.set(): 将私有变量设置到线程。(给线程中存线程变量的)
  • 2.get(): 获取线程中的私有变量。
  • 3.remove(): 将私有变量从线程中移除。
public class ThreadPoolDemo {
    private static ThreadLocal<SimpleDateFormat> threadLocal=new ThreadLocal<>();
    public static void main(String[] args) {
        //设置私有变量
        threadLocal.set(new SimpleDateFormat("mm:ss"));
        //得到ThreadLocal
        SimpleDateFormat simpleDateFormat=threadLocal.get();
        Date date=new Date(1000);
        String result=simpleDateFormat.format(date);
        System.out.println("时间:"+result);
    }
}
//创建线程私有变量
    private static ThreadLocal<String> threadLocal=new ThreadLocal<>();
    public static void main(String[] args) {
        //定义公共任务
        Runnable runnable=new Runnable() {
            @Override
            public void run() {
                //的到线程名称
                String tname=Thread.currentThread().getName();
                System.out.println(tname+"设置值:"+tname);
                //设置线程的名称
                threadLocal.set(tname);
            }
        };
        Thread t1=new Thread(runnable);
        t1.start();
        Thread t2=new Thread(runnable);
        t2.start();
    }

运行结果:
在这里插入图片描述运行多次后:
在这里插入图片描述

(3)ThreadLocal的创建和初始化

  • 1.当ThreadLocal中出现set方法后,所有类型的初始化方法就不会执行了。
  • 原因:ThreadLocal在执行get方法的时候,才会判断并调用初始化方法。

在这里插入图片描述创建并初始化ThreadLocal:
在这里插入图片描述

(4)ThreadLocal的底层实现及使用场景

  • ThreadLocal的底层是由ThreadLocalMap实现的。
  • ThreadLocal的底层使用斐波那契数(黄金分割数) 实现hash算法。

在这里插入图片描述ThreadLocal经典使用场景:

  • 1.解决线程不安全的问题。
  • 2.线程级别的数据传送(可以实现一定程度的解耦

(5)ThreadLocal的缺点

1.不可继承性(子线程不能继承主线程的私有变量)。

public class threadLocalDemo76 {
    private static ThreadLocal threadLocal=new ThreadLocal();

    public static void main(String[] args) {
        //在主线程里面设置值
        threadLocal.set("Java");
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("获取值:"+threadLocal.get());
            }
        });
        t1.start();
    }
}

在这里插入图片描述
解决方案:InheritableThreadLocal

public class ThreadLocalDemo77 {
    private static ThreadLocal threadLocal=new InheritableThreadLocal();

    public static void main(String[] args) {
        //在主线程里面设置值
        threadLocal.set("Java");
        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("获取值:"+threadLocal.get());
            }
        });
        t1.start();
    }
}

在这里插入图片描述
2.脏读(一个程序的任务执行当中,读取了上一个(其他)任务的数据)。
(1)脏读产生的原因:

线程池复用了线程,和这个线程相关的静态属性也复用,所以就导致了脏读。
在这里插入图片描述

(2)脏读的解决方法:

  • 1.避免使用静态变量。
  • 2.使用完后,执行**remove()**方法
public class ThreadLocalDemo80 {

    static ThreadLocal<String> threadLocal=new ThreadLocal<>();

    public static void main(String[] args) {
        ThreadPoolExecutor threadPoolExecutor=new ThreadPoolExecutor(1,1,0,
                TimeUnit.SECONDS,new LinkedBlockingDeque<>(1000));
        for (int i = 0; i < 2; i++) {
            threadPoolExecutor.submit(new MyThreadLocal());
        }

        Thread t1=new Thread(new Runnable() {
            @Override
            public void run() {
                MyThreadLocal myThreadLocal=new MyThreadLocal();
                myThreadLocal.show();
            }
        });
        t1.start();
        Thread t2=new Thread(new Runnable() {
            @Override
            public void run() {
                MyThreadLocal myThreadLocal=new MyThreadLocal();
                myThreadLocal.show();
            }
        });
        t2.start();
    }
    static class MyThreadLocal extends Thread{
        private static boolean flag=false;
        public void show(){
            String tname=this.getName();
            String rname=Thread.currentThread().getName();
            if(!flag){
                threadLocal.set(tname);
                System.out.println(tname+"设置了:"+tname);
                flag=true;
            }
            System.out.println(tname+"得到了:"+threadLocal.get());
            threadLocal.remove();
        }
    }
}

在这里插入图片描述
3.内存溢出。使用remove()方法释放资源
(1)原因:

  • 1.线程池是长生命周期。当一个线程使用完资源之后,没有释放资源,或者说释放资源不及时就会导致内存溢出。
  • 2.ThreadLocal -> ThreadLocalMap -> Entry[] ->key(ThreadLocal),value(1mb资源)->(垃圾回收器就不会回收value资源)

(6)为什么ThreadLocal会将key设置为弱引用?

答:ThreadLocal为了更大程度的避免OOM
在这里插入图片描述

(7)Java引用类型(4种)

在这里插入图片描述

(8)哈希冲突的解决方案是不同

在这里插入图片描述

以上是关于线程安全的解决方案(ThreadLocal详解)的主要内容,如果未能解决你的问题,请参考以下文章

线程安全的解决方案(ThreadLocal详解)

ThreadLocal详解,处理成员变量线程不安全的情况

Java中的ThreadLocal详解

ThreadLocal详解

浅谈(Java)并发ThreadLocal

18.一篇文章,从源码深入详解ThreadLocal内存泄漏问题