这里做的笔记是结合JVM中的java内存模型
和java并发编程艺术中讲的java内存模型
再结合一些面试题
JVM内存区域和JAVA内存模型有明显的区别
要分清他们之间的关系
JMM是一种规则,主要是研究并发多线程内存的可见性
是一种高速缓存进行读写访问的过程抽象
目的就是保证程序运行时内存应该是内存一致的
Java内存模型为每个线程都创建了一个工作内存
但是所有的变量都应该存储在主内存中,这里的主内存不是硬件上的内存。
但使用Volatile变量的计算并不是线程安全的
例如如果多线程对volatile变量自增而不加锁,就会同步出错
因为
num++其实是由4个字节码操作组成的。位于栈顶的num很可能被其他线程加大。
问题解决:
1.可以使用AtomicInteger 自增操作则是 incrementAndGet()方法
2.方法synchronized
使用场景:
1.运算结果不依赖当前的值,或者单线程改变 比如set get 方法 不依赖原值
2.变量不需要其他线程参与变量的约束
volatitle boolean config = false ;
finishconfig(config);
config = true;
while(!config){
sleep();
}
dosomethings();
如果 config 不是Volatile变量
config可能比 fhinishconfig还快
这就可能导致还没配置就执行其他的了。
总结一下volatile 变量就三个特性
本身的读和写是原子的,其他的不是比如自增原子性
对Volatile变量操作是所有线程可见的,强制刷新缓存 可见性
Volatile 变量不可以被重排序添加内存屏障 有序性
锁的释放和获取本质上就是消息通知
一个线程释放锁,是告诉下一个获取线程的锁消息。
一个线程获取锁,其实是接受了某个线程发出的锁消息。
sychronized 本质上就是加锁 和 lock 方法类似
sychronized 里的操作是原子的,因为只有一个线程执行原子性
sychronized 操作完是对所有线程可见的 对一个变量unlock会同步变量到主内存可见性
sychronized 里的代码不能被重排序因为只有一个线程执行 有序性
一些锁的实现本质上就是 修改Volatile变量 进行CAS操作来达到内存一致性的
final不同于锁和Volatile
final域访问类似普通变量
但 final 域必须遵守两个重排序规则
// 一个对象
static Ex object;
// 构造函数
public Ex(){
// i 是 final域
i=0;
// j 是普通域
j=2;
}
// A线程写
public void write(){
object = new Ex();
}
// B 线程读
public void read(){
Ex obj = object ;
int a = obj.i;
int b = obj.j;
}
看上面这个例子
B 线程对 obj.i 即final 域 一定读出来是 构造函数初始化过后的值。
而对 obj.j 即普通域不一定读出来初始化的值,而可能是默认初值。
final 域保证对象的值或者引用一定是正确初始化过后的值
final域保证读对象的final域之前一定先读对象的引用
先行规则是判断数据是否存在竞争,线程是否安全的重要原则。
double check lock简称 DCL是非常常见的延迟加载技术
即 对于某些开销大的对象,在使用的时候才加载。按需加载。也叫懒加载
下面是线程不安全的初始化对象
public class LazyInital {
class Instatnce{
}
private static Instatnce instatnce;
public Instatnce getInstatnce(){
if(instatnce==null) // Thread A
instatnce = new Instatnce(); // Thread B
return instatnce;
}
}
简单的改就在getInstance 方法上加sychronized标识即可。
但这样性能不好
下面是DCL的错误版本
public Instatnce getInstatnce(){
if(instatnce==null){
synchronized (LazyInital.class){
if(instatnce==null)
instatnce = new Instatnce();
}
}
return instatnce;
}
这样就叫双重检查锁定 看上去很舒服但是有大问题
java对象的初始化 分为3个部分
2 和3 是可能被重排序的。
最后的一种情况可能是
线程A 还没有初始化instance 线程B就已经拿到对象的引用了,此时返回的是一个没有初始化的对象
使用 Volatile的语义即可
加上Volatile 描述
使用类初始化的解决方法,依靠JVM初始化类的对象加锁的特性
public class InstanceFactory {
static class Instance{
}
// instanceHolder类的初始化加锁
private static class InstanceHolder{
private static Instance instance = new Instance();
}
public static Instance getInstance(){
return InstanceHolder.instance;
}
}
volatile的本质是告诉jvm当前变量在工作内存中的值不确定,要从主内存中读取
而synchronized是锁定当前变量,只能由一个线程读取,其他线程阻塞,直到锁释放。
Volatile 变量级别,synchronized 方法变量
Volatile只能实现变量修改的可见性,不能保证操作的原子性。比如Volatile的读和写是原子的,但Volatile操作不一定是原子的,自增。
Volatile不会阻塞,synchronized会阻塞
volatitle变量不会优化,synchronized会优化