前言:好久没更文啦,最近打算把前面总结的一些文章发出来~
之前在一次开发中进行时间格式化,遇到了线程安全问题,遂小结一下
SimpleDateFormat
-
现象
-
SimpleDateFormat类图
该图片摘自网络,侵删!
-
线程不安全因素
多个线程同时调用parse时,可能发生:
- 返回数据又被clear清空的对象
- 设置好的cal对象又被其它线程修改
- 解决方式
- 创建局部变量(缺点:每次使用都需 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)
引用队列可以与软引用、弱引用以及虚引用一起配合使用,当垃圾回收器准备回收一个对象时,如果发现它还有引用,那么就会在回收对象之前,把这个引用加入到与之关联的引用队列中去
与软引用、弱引用不同,虚引用必须和引用队列一起使用