以ThreadLocal为例理解java的内存模型

ThreadLocal含义

ThreadLocal意为线程局部变量,只在线程的生命周期内起作用,当用ThreadLocal维护变量时,每个线程都会创建属于自己的变量副本,所有读写操作均只对变量副本生效而不会影响其他线程,是绝对的线程安全。

这里我们对比下普通变量在多线程的情况下所产生的线程安全问题,当多个线程对同一内存区域的数据进行运算时即会产生线程安全问题,这也正是线程的内存模型所导致的!下面阐述下其大致模型

 

线程内存模型

首先,jvm将内存组织分为主内存和工作内存,主内存主要包括本地方法区和堆。每个线程都有一个工作内存,工作内存中主要包括两个部分,一个是属于该线程私有的栈和对主存部分变量拷贝的寄存器(包括程序计数器PC和cup工作的高速缓存区)。 下图是主内存与工作内存之间的关系:

以ThreadLocal为例理解java的内存模型_第1张图片

下图表明了线程、工作内存、主内存之间的关系:

以ThreadLocal为例理解java的内存模型_第2张图片

jvm规定所有的变量都存储在主内存中,每个线程有各自的内存空间即工作内存,工作内存中保存了该线程需要使用到的变量的主内存副本拷贝,对变量的操作(读取、赋值),线程都是在自己的工作内存中进行,而不允许直接对主内存的变量进行操作,运行结束之后回写到主内存中。不同线程是无法访问到互相的工作内存,线程间变量值得传递均需通过主内存来完成。例如有个变量int  a = 1,当线程1读取并修改为2,此时如果线程1未回写到主内存中,线程2读取时仍是1,只有当线程1回写完成时才能保证接下来的线程能读取到最新值,这里又产生一个概念即可见性。所以我们需要线程的同步机制来保证多线程操作同一内存区域的读写控制

 

原子性、可见性、有序性

原子性:基本数据类型的读写操作是原子性的;更大范围的(代码块)的原子性可以用lock、unlock操作来实现(上锁后就只有一个线程来执行了,所以不会被其他线程打断原子操作),表现到代码层面就是使用syncrhoized同步块。

可见性:当一个线程修改了被多线程所共享的一个主内存变量值时,其他线程能立刻知道这个值。

有序性:多线程之间对共享数据的操作的有序性,可以通过volatile和syncrhoized关键字来保证。volatile关键字禁止了指令重排序,而syncrhoized关键字规定了多个线程每次只能有一个线程对共享数据进行操作。

 

指令重排序

需要注意的是,java编译器为了提高性能,采取了指令重排序,当多个线程都有语句对同一内存区域进行操作的话,有可能因为指令重排序而导致得到的结果出乎预料,因此也需要线程的同步机制来保证多线程操作同一内存区域的读写控制

这个指令重排序的坑在项目中其实我已经遇到过一次,在创建工单时由于指令重排序,明明应该先执行的语句却在后面获取不到值,当时一开始还看了编译后的class文件,以为是编译的问题,结果编译后代码语句顺序也是对的,后来才意识到是指令重排序的坑。当时我的解决方案是通过CopyOnWriteArrayList先临时存储然后再获取。

 

最后总结

1.所有的变量都存储在主内存中(虚拟机内存的一部分),对于所有线程都是共享的。 
2.每条线程都有自己的工作内存,工作内存中保存的是主存中某些变量的拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。 
3.线程之间无法直接访问对方的工作内存中的变量,线程间变量的传递均需要通过主内存来完成。

其实到这里,使用了ThreadLocal维护变量与普通变量的情况很明显,一个是变成线程独有,一个是多线程共享,至此应该对线程的内存模型也有了比较深刻的理解。

你可能感兴趣的:(jvm)