线程安全的解决方案(ThreadLocal详解)
Posted 秃头小宝儿
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程安全的解决方案(ThreadLocal详解)相关的知识,希望对你有一定的参考价值。
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详解)的主要内容,如果未能解决你的问题,请参考以下文章