我们知道多个线程同时读写同一共享变量会导致并发问题。
一种解决方案是使用 Immutability 模式,如果共享变量在初始化之后就不会改变,只能读取,那么无论多少个线程同时读这个共享变量都不会出现并发问题。比如说 Java 中的 Long、Integer、Short、Byte 等基本数据类型的包装类的实现。
另一种解决方案是突破共享变量,没有共享变量就不会有并发问题。那么如何避免共享呢?思路其实很简单,就是每个线程拥有自己的变量,彼此不共享,就不会有共享问题。
具体来说有两种方法:线程封闭和线程本地存储(ThreadLocal)。
线程封闭
方法里的局部变量,因为不会和其他线程共享,所以没有并发问题,叫做线程封闭,比较官方的解释是:仅在单线程内访问数据。由于不存在共享,所以即便不同步也不会有并发问题,性能杠杠的。
如上图所示,JVM 中每一个线程都会有一个 Java 虚拟机栈。Java 程序每调用一个方法都会入栈,每执行完一个方法都会出栈,且每一个方法都有一个栈帧。栈帧中存储了参数、局部变量和返回地址。由此可见,方法的局部变量是不可能和其它线程共享的。
局部变量可以避免线程共享,此外还有什么方法能避免线程共享么?有,那就是 Java 语言提供的线程本地存储(ThreadLocal)。
ThreadLock
SimpleDateFormat 不是线程安全的,如果想在并发场景下使用它可以将其设置为一个方法的局部变量,这样就会创建许多对象占用内存。或者使用 ThreadLock,不同线程调用 SafeDateFormat
会返回不同的 SimpleDateFormat 对象,但是由于在线程之间不共享,就和局部变量一样是安全的。
|
|
接下来再看一下 ThreadLock 的代码实现。
|
|
在 Java 的实现方案中,ThreadLocal 仅仅是一个工具类,内部并不持有和线程有关的数据,所有和线程有关的数据都存储在 Thread 中,比如 Thread 内部持有当前线程的 ThreadLocalMap。
这样设计有一个好处就是能够避免内存泄露。