初识线程Ⅴ之ThreadLocal

17 篇文章 0 订阅

回顾

线程不安全解决方案

  1. CPU抢占式执行(不可解决)
  2. 加锁(synchronizd、Lock)
  3. 私有变量(ThreadLocal 线程本地变量)
    ThreadLocal:线程级别的私有变量
    与任务级别的私有变量完全不同

eg:线程池:10,任务数:1000

  • 任务级别的私有变量:会创建1000个私有变量
  • 线程级别的私有变量:会创建10个私有变量
public void method(){
	MyObject obj = new MyObject();
	//......
	//任务级别:会创建1000个私有变量
	//线程级别:会创建10个私有变量
}

需求1:使用最高效的方式实现2个Date(时间类型)的格式化

需求2:使用最高效的方式实现10个Date(时间类型)的格式化

需求3:使用最高效的方式实现1000个Date(时间类型)的格式化

出现线程不安全问题
正常情况:
在这里插入图片描述
异常情况:
在这里插入图片描述

解决
①加锁,带来的新的问题:排队执行的性能消耗问题
解决线程不安全,也不排队执行:
②ThreadLocal :线程级别的私有变量

选择①或②的条件:看线程级变量的复用率,如果复用率较高,使用ThreadLocal,复用率较低,就使用加锁

ThreadLocal

使用方法

  1. set(T):将私有变量存放到线程中
  2. get():从线程中取得私有变量
  3. remove():从线程中移除私有变量(脏读、内存溢出)
    remove 方法在任何场景都有意义,且不可省略
  4. initialValue():初始化方法1

注意:initialValue返回的数据类型一定要和 ThreadLocal 定义的泛型类型保持一致。
在这里插入图片描述

initialValue + get

  • 正常存取操作

initialValue + set + get ?

  • 先执行set方法,再执行get方法(不执行initialValue方法)

什么情况下不会执行initialValue?为什么?

  • 调用set之后就不会执行了
  • threadLocal是懒加载的,当调用了get方法之后,才会尝试执行initialValue方法,尝试获取一下ThreadLocal set 的值,如果获取到了值,那么初始化方法永远不会执行

ThreadLocal ->Map(ThreadLocalMap)->Entry[] -> key(ThreadLocal),value(实际存储值)

get
在这里插入图片描述
set
在这里插入图片描述

  1. withInitial():初始化方法2
static ThreadLocal<String> threadLocal = ThreadLocal.withInitial(()->"java");

初始化没值就执行initial方法
每次remove之后会执行一次

使用场景

  1. 解决线程安全的问题
  2. 实现线程级别的数据传递

多个线程 ->每个线程拥有自己的变量(ThreadLocal)

ThreadLocal只能存储一个值

缺点

不可继承

子线程中不能读取到父线程的值

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

解决:InheritableThreadLocal

InheritableThreadLocal不能实现不同线程之间的数据共享

脏读(脏数据)

在一个线程中读取到了不属于自己的数据

线程使用ThreadLocal 不会出现脏读,因为每个线程都使用的是自己的变量值和ThreadLocal

线程池使用ThreadLocal 会出现脏读,因为线程池会复用线程,复用线程后,也会复用线程中的静态属性,从而导致某些方法没有被执行,于是就出现了脏数据的问题

解决方案:
避免使用静态属性(静态属性在线程池中会被复用)
使用remove解决

内存溢出问题

(最常出现的问题)

打开数据连接但未关闭

内存溢出:当一个线程执行完后,不会释放这个线程所占内存,或者释放内存不及时的情况就叫做内存溢出(线程不用了,但是相关内存得不到及时释放)

内存溢出:ThreadLocal+线程池

线程池是长生命周期
线程是执行完任务线程就结束了,线程相关的资源都会释放掉

ThreadPool ->Thread -> ThreadLocal ->内存不会关闭 -> OOM

Thread -> ThreadLocalMap—>开放寻址法

升级:(链表–>红黑树)
链表长度大于8
数组长度大于64

降级:(红黑树–>链表)
链表长度小于6

HashMap和ThreadLocalMap处理哈希冲突的区别?

  • HashMap使用链表法
  • ThreadLocalMap使用开放寻址法

为什么要这么实现?

  • 开放寻址法的特点和使用场景是数据量比较少的情况下性能更好
  • HashMap里边存储的数据通常情况比较多,这个时候使用开放寻址法效率就比较低了

为什么将ThreadLocal中的key设置为弱引用

  • 为了最大程度的避免OOM

为什么会发生OOM?

  • ThreadLocal(长生命周期)–>Thread -> ThreadLocal ->Entry[] -> Entry -> key,value(强引用)

解决ThreadLocal的内存溢出
使用remove()

提升程序的性能

多线程
单例模式

设计模式

  1. 单例模式(手写)
  2. 工厂模式(简单工厂、抽象工厂)
  3. 模板模式
     
单例模式

在整个程序的运行中,只存在一个对象

饿汉方式:直接先创建一个对象

  • 优点:不用加锁也是线程安全的
  • 缺点:程序启动之后就会创建,但是创建完了之后可能不会使用,浪费了系统资源

懒汉方式:当程序启动之后,并不会进行初始化,而是在什么时候调用再进行初始化

非安全的单例模式 -----> 懒汉
解决方案:加锁
在这里插入图片描述
①先在内存中开辟空间
②初始化
③将变量singleton指向内存区域

指令优化(指令重排序)
指令重排序前:①->②->③
指令重排序后:①->③->②

执行1、3–>暂停执行了–>未执行初始化–>返回一个空对象–>线程不安全

自定义阻塞队列

生产者消费者模型
生产者生产数据,消费者消费生产者生产的数据

生产者:当数据满了之后,不要尝试给队列添加数据,而是阻塞等待

sleep(time)
wait()/notify()/notifyAll()
LookSupport park()/unpark()

消费者:当队列为空的时候,阻塞等待

自定义阻塞队列
实现队列:链表、数组(栈)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值