概念
多线程中的变量
首先我介绍的是volatile关键字,其次是原子变量,最后则是ThreadLocal线程本地变量
java基本内存模型
用到volatile这个关键字以及后面的原子变量之前,我们必须先了解一下什么是java基本内存模型。
先明确几个概念:
主内存:主内存就是所有线程共享的内存,对于一个共享变量来说,主内存存放其真实数据(本尊数据)
线程工作内存:线程对数据操作时,都会有自己的工作内存,对共享变量操作前,会先从主内存中获取到值,操作完后在回写回去。
volatile
现在有2个线程A,B,他们要主内存中间的一个变量s=0;此时A线程要修改这个共享变量,它是先获取到这个值复制到线程工作内存里面去,然后在线程工作内存里面把这个值修改了,然后把这个值再写到主内存里面去。此时B读取这个s变量,那么值可能是0,也可能是线程A所修改的值。
使用volatile这个关键字可以避免上述这种情况(使用锁来对变量加锁或者synchronized开销太大)。
针对上述的例子,volatile的可见性保证了不会出现上述问题。
什么是可见性呢?
当一个线程修改了变量的值,新的值会立刻同步到主内存当中。而其他线程读取这个变量的时候,也会从主内存中拉取最新的变量值。
当然这种可见性不是原子性哦。当遇到以下情况会有问题
1.多个线程同时修改变量且修改时依赖变量本身。
2.多个volatile变量维护一个条件,若是别的线程对其多个变量修改,那么可能造成条件的不成立。
针对上述情况于是有了原子变量(后面介绍),保证了其原子性。
volatile还有一个特性就是禁止指令重排序。那么什么是指令重排序呢?
指令重排序:编译器的字节码的重排序。cpu指令的重排序。
指令重排序的目的是在不改变单线程下程序的逻辑下,优化程序执行效率。对于多线程于是就有了问题。有的程序时单线程下,调换一下顺序也没什么的,但是,对于多线程,调换一下顺序,可能回到其他线程造成大的影响。
而volatile则是解决了这个问题,他利用了内存屏障来来辅助解决了这个。
原子变量
说到原子变量就不得不说CAS。
什么是CAS呢?
就是更新一个值的时候,查询内存中的值,和自己要更新前获取到的值是否一致,若是一致,那么更新。
与synchronized相比,cas是乐观锁,我认为并发不会修改到我的值,不加锁,只是提前获取到值,要更新的时候在比对一下,若是内存的值和我的值一致,那么更新,否则不更新。而synchronized则是不管什么直接加锁的。因此是悲观锁。
什么是ABA问题?
3个线程A,B,C对cas变量a修改。A,B线程获取到了变量a,A修改变量为b,B线程阻塞,C线程获取到变量b,并把b改成了a,B线程不阻塞了,继续执行,执行成功,这就是ABA问题。这个B线程不应该执行的,但是还是执行了。如这个变量是个对象,其引用没有变化,但是具体指变了,那么会出大问题的。解决方案就是在cas变量上加个版本号或者时间戳来限定。
具体demo就是Atomic开头的类。具体我就不详细说了。
与加锁相比这个更加轻量级。
ThreadLocal
线程本地变量是说,每个线程都有同一个变量的独有拷贝ThreadLocal是一个泛型类,接受一个类型参数T,它只有一个空的构造方法。这个直接看个demo
package thread;public class ThreadLocal001 {
static ThreadLocalpublic static void main(String[] args) throws InterruptedException { Thread child = new Thread() { @Override public void run() { System.out.println("child" + local.get()); local.set(200); System.out.println("child" + local.get()); } }; local.set(100); child.start(); child.join(); System.out.println("main" + local.get()); }
}
结果如下:
childnull
child200
main100
这说明,main线程对local变量的设置对child线程不起作用,child线程对local变量的改变也不会影响main线程,它们访问的虽然是同一个变量local,但每个线程都有自己的独立的值,这就是线程本地变量的含义。