简单的先描述下线程安全和线程不安全的概念:
- 线程安全:线程安全其实就是多线程访问同一段代码,不会产生不确定的结果,那么就叫线程是安全的;如果说你的程序在多线程执行和单线程执行的情况下永远都能获得一样的结果,那么你的线程就是安全的。
- 线程不安全:多线程操作共享数据,造成数据不一致的问题;
一、线程安全级别
- 不可变:像String、Integer、Long这些,都是final类型的类,任何一个线程都改变不了他们的值,要改变除非新创建一个,因此这些不可变的对象不需要任何同步手段就可以在多线程环境下使用;
- 绝对线程安全:不管运行环境如何,调用者都不需要额外的同步措施;要做到这一点通常需要付出许多额外的代价,Java中标注自己是线程安全的类,实际上绝大多数都不是线程安全的,不过在Java中绝对线程安全的类也有,比如:CopyOnWriteArrayList、CopyOnWriteArraySet;
- 相对线程安全:相对线程安全通常意义上就是我们所说的线程安全,像Vector这种,add、remove都是原子操作,不会被打断,但也就仅限于此;如果有个线程在遍历vector,有个线程同一时间在add这个Vector,99%的情况下都会出现ConcurrentModificationException,也就是fail-fast极值;
- 线程非安全:ArrayList、LinkedList、HashMap等其实都是线程非安全的类
二、线程安全需要保证几个基本特性
三个基本特性:原子性、可见性、有序性
- 原子性:简单来说,就是当前线程相关操作不会中途被其他的线程干扰,一般采用同步机制实现;
- 可见性:是指如果一个线程操作了某个共享变量,那么其状态能被其他线程知晓;通常被解释为将线程本地状态反映到主内存上,volatile属性就是保证可见性的;
- 有序性:是保证线程内串行语义,避免指令重排等;
三、如何实现线程安全
- 锁:
- 悲观锁:synchronized,lock
- 乐观锁:CAS
- 根据自己的业务情况,选择ThreadLocal,让每个线程玩自己的数据
ThreadLocal原理:
ThreadLocal可以理解为线程本地变量,他会在每个线程都创建一个副本,那么线程直接访问内部副本变量就可以了,做到了线程之间的相互隔离,相比于synchronized的做法就是用空间来换时间;
ThreadLocal有一个静态的内部类ThreadLocalMap,ThreadLocalMap内部又包含了一个Entry数组,Entry本身是一个弱引用,他的Key是指向ThreadLocal的弱引用,Entry具备了保存key-value 键值对的能力;
Entry采用弱引用的目的就是为了防止内存泄漏,如果此处是强引用的话,那么ThreadLocal对象除非线程结束否则无法被回收,但是如果是弱引用则会在下一次GC的时候就被回收掉,但是这样也还是会有一些内存泄露的风险,如果key和ThreadLocal对象被回收之后,entry中存在key为null,但是value有值的entry对象,这样就会永远无法被访问到,同样除非线程结束,否则无法被回收;但是只要ThreadLocal使用恰当,在使用完之后及时调用remove方法删除Entry对象,实际上不会出现上述问题;