线程安全问题

前言:好久没更文啦,最近打算把前面总结的一些文章发出来~
之前在一次开发中进行时间格式化,遇到了线程安全问题,遂小结一下

SimpleDateFormat

  1. 现象
    在这里插入图片描述在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  2. SimpleDateFormat类图
    该图片摘自网络,侵删!
    在这里插入图片描述

  3. 线程不安全因素
    在这里插入图片描述
    在这里插入图片描述
    多个线程同时调用parse时,可能发生:

  • 返回数据又被clear清空的对象
  • 设置好的cal对象又被其它线程修改
  1. 解决方式
  • 创建局部变量(缺点:每次使用都需 new一个对象,开销大)
  • 加锁(缺点:高并发情况下性能较差,多个线程需要竞争锁,每次需要等待锁释放)
  • ThreadLocal
  • 使用替代工具类-DateTimeFormatter
    在这里插入图片描述
    在这里插入图片描述

线程安全

线程不安全的两个必要条件

  • 多线程之间的变量(资源)是共享的
  • 多线程之间对共享变量(资源)存在修改操作

共享资源和私有资源

  • 形参、局部变量都存放在栈帧中,是线程私有的
  • 属性存放在对象里,对象(不包括反射对象)存放在堆中,即属性是线程之间共享的
  • 静态属性在类中,类在方法区中,静态属性也是线程之间共享的

线程的三个特性

原子性

操作是不可分的

int x = 7;     是    
int y = x ;    否   
x++;           否   
x = x+1;       否 

Java有两种方式实现原子性:
一种是使用锁,另一种是使用处理器的CAS
eg:采用synchronized(是独占锁)、采用Lock、采用AtomicInteger(内部实现-非阻塞的CAS算法)

可见性

一个线程对共享变量的修改,对于另一个线程来说是否是可以看到的
对于jvm来说,主内存是所有线程共享的java堆,而工作内存中的共享变量的副本是从主内存拷贝过去的,是线程私有的局部变量,位于java栈中
volatile关键字,或者使用锁的机制,就能实现内存的可见性

在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须存在happens-before关系(偏序关系)
eg:对某个volatile字段的写操作happens-before后续对同一个volatile字段的读操作

happens-before规则不是描述实际操作的先后顺序,它是用来描述可见性的一种规则

  • 程序顺序规则:一个线程中的每个操作,happens-before于该线程中的任意后续操作
  • 监视器锁规则:对一个锁的解锁,happens-before于随后对这个锁的加锁
  • volatile变量规则:对一个volatile域的写,happens-before于任意后续对这个volatile域的读
  • 传递性:如果A happens-before B,且B happens-before C,那么A happens-before C。
  • start()规则:如果线程A执行操作ThreadB.start()(启动线程B),那么A线程的ThreadB.start()操作happens-before于线程B中的任意操作
  • join()规则:如果线程A执行操作ThreadB.join()并成功返回,那么线程B中的任意操作happens-before于线程A从ThreadB.join()操作成功返回
  • 程序中断规则:对线程interrupted()方法的调用先行于被中断线程的代码检测到中断时间的发生
  • 对象finalize规则:一个对象的初始化完成(构造函数执行结束)先行于发生它的finalize()方法的开始

有序性

编译器或运行时为了效率可以在允许的时候对指令进行重排序
该图片内容摘自网络,侵删!
在这里插入图片描述

若A发生重排序,那么线程B就会拿到一个未初始化的content去配置,从而引起错误

volatile和synchronized
volatile关键字本身就包含了禁止指令重排序的语义,而synchronized则是由“一个变量在同一时刻只允许一条线程对其进行lock操作”这条规则来获得的,这个规则决定了持有同一个锁的两个同步块只能串行地进入

ThreadLocal

what

ThreadLocal是JDK包提供的,它提供线程本地变量,如果创建一个ThreadLocal变量,那么访问这个变量的每个线程都会有这个变量的一个副本,在实际多线程操作的时候,操作的是自己本地内存中的变量,从而规避了线程安全问题
主要用做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的

how

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

源码

在这里插入图片描述

  • set方法
    在这里插入图片描述
    在这里插入图片描述

  • get方法
    在这里插入图片描述
    在这里插入图片描述

  • remove方法
    在这里插入图片描述

总结

在这里插入图片描述

注意事项

内存泄露问题
该图片摘自网络,侵删!
在这里插入图片描述
在这里插入图片描述

每个线程Thread都维护了自己的threadLocals变量
在这里插入图片描述

解决方式:使用完ThreadLocal后,执行remove操作,避免出现内存溢出情况

扩展

ThreadLocal在Java中的应用

  • Spring框架的事务主要是由ThreadLocal和AOP实现,实现事务隔离级别,
    在TransactionSynchronizationManager类中

  • Spring采用Threadlocal的方式,来保证单个线程中的数据库操作使用的是同一个数据库连接,同时,采用这种方式可以使业务层使用事务时不需要感知并管理connection对象,通过传播级别,巧妙地管理多个事务配置之间的切换,挂起和恢复

  • cookie,session等数据隔离都是通过ThreadLocal去实现的

Java四大引用

  • 强引用(Strong Reference)
    只要强引用存在,垃圾回收器将永远不会回收被引用的对象

  • 软引用(Soft Reference) java.lang.ref.SoftReference
    在内存足够的时候,软引用对象不会被回收,只有在内存不足时,系统则会回收软引用对象,如果回收了软引用对象之后仍然没有足够的内存,才会抛出内存溢出异常

  • 弱引用(Weak Reference)java.lang.ref.WeakReference
    无论内存是否足够,只要 JVM 开始进行垃圾回收,那些被弱引用关联的对象都会被回收

  • 虚引用(Phantom Reference)PhantomReference
    如果一个对象仅持有虚引用,那么它就和没有任何引用一样,它随时可能会被回收

  • 引用队列(ReferenceQueue)
    引用队列可以与软引用、弱引用以及虚引用一起配合使用,当垃圾回收器准备回收一个对象时,如果发现它还有引用,那么就会在回收对象之前,把这个引用加入到与之关联的引用队列中去
    与软引用、弱引用不同,虚引用必须和引用队列一起使用

ThreadSafeSimpleDateFormat

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值