并发编程基本设计方案 线程本地存储模式(没有共享,就没有伤害)
上篇我们提到,并发编程中,并发问题之所以会产生,是因为多个线程对一个资源进行读写操作,即共享 + 读写,在上篇中,我们了解到不可变模式通过破坏读写这个条件,避免的并发问题的产生,那么今天我们要讲的是通过限制共享,来避免并发问题的产生。
在讲之前,我们先来补一补JVM的知识,我们知道JVM有三大子系统,类加载器,字节码执行引擎,以及运行时数据区,而今天我们来了解一下运行时数据区,我们从线程的视角来看看运行时数据区究竟有什么?在线程的眼中,运行时数据区,可以被分为两块区域,一块是我自己独享的,一块是我和其他线程共享的,独享的区域包括 虚拟机栈,本地方法栈,程序计数器,共享的区域包括 方法区(元数据区)堆内存。简单介绍一下这些区域的作用,方法的调用需要借助栈这种数据结构,Java这门编程语言是可以借助一些机制调用本地方法,就是Native Method,就是用C、C++写的函数,于是栈就被分为了两个,虚拟机栈主要就是用于维护Java方法的调用关系,而本地方法栈就是维护Native Method的调用关系,程序计数器就是记录当前执行到整个流程中的哪一步了。方法区(元数据区)记录了类的元信息,比如类的名字,类的字段,类的方法,堆内存就是存储对象实例的地方。
好,知识补充到此为止,到此,你已经知道了,一个线程的创建,必然会伴随着虚拟机栈,本地方法栈以及程序计数器的分配,而并发问题产生的一个条件就是,多个线程在共享一块数据,就是同一块内存区域,那么今天所说的限制共享,就是将数据移动到线程的独享区中,这样其他线程就无法访问了,从而避免的并发问题的产生。线程是执行方法的直接载体,将数据移动到线程的独享区,对应到代码中,就是将数据移入到局部变量中,这样其他线程就访问不到,限制了共享。但是这种方法存在比较严重的问题,那就是对象的重复创建,比如,有10个线程,调用A方法3次,A方法中利用局部变量保存一个对象,随着线程的执行,对象就是被重复创建,创建的个数与调用次数成正比,这对于JVM来说,是难以接受的。于是我们可以放开一点限制,数据不一定要保存在局部变量中,我们的核心目的是在线程层面,数据独享,为了达到这个目的,Java给我们提供了一个ThreadLocal ,这个工具类可以帮我们把数据维护成线程独享。但是值得注意的是,ThreadLocal仅仅只是一个工具类,并不负责数据的持有,真正持有数据的其实是Thread
对于ThreadLocal具体怎么用,网上已经有很多教程了,你也可以尝试看看源码,至于ThreadLocal的缺点,我们下期来简单聊聊。
