可见性:一个线程对共享变量的修改,更够及时的被其他线程看到
原子性:即不可再分了,不能分为多步操作。比如赋值或者return。比如"a = 1;"和 "return a;"这样的操作都具有原子性。类似"a += b"这样的操作不具有原子性,在某些JVM中"a += b"可能要经过这样三个步骤:
1. 取出a和b 2. 计算a+b 3. 将计算结果写入内存 复制代码
Synchronized能够实现原子性和可见性;在Java内存模型中,synchronized规定,线程在加锁(对象锁,类锁)时的工作步骤:
PS:
当synchronized修饰一个static方法时,多线程下,获取的是类锁(即Class本身,注意:不是实例);
当synchronized修饰一个非static方法时,多线程下,获取的是对象锁(即类的实例对象)
Volatile实现内存可见性是通过内存屏障store和load指令完成的;
对volatile变量执行写操作时,会在写操作后加入一条store指令,即强迫线程将最新的值刷新到主内存中;
而在读操作时,会加入一条load指令,即强迫从主内存中读入变量的值。但volatile不保证volatile变量的原子性,例如:
Private int Num=0; Num++;//Num不是原子操作 复制代码
Num++不是原子操作,因为其可以分为: 读取Num的值,将Num的值+1,写入最新的Num的值 。
对于Num++;操作,线程1和线程2都执行一次,最后输出Num的值可能是:1或者2
【解释】
输出结果1的解释:当线程1执行Num++;语句时,先是读入Num的值为0,倘若此时让出CPU执行权,线程获得执行,线程2会重新从主内存中,读入Num的值还是0,然后线程2执行+1操作,最后把Num=1刷新到主内存中;线程2执行完后,线程1由开始执行,但之前已经读取的Num的值0,所以它还是在0的基础上执行+1操作,也就是还是等于1,并刷新到主内存中。所以最终的结果是1.一般在多线程中使用volatile变量,为了安全,对变量的写入操作不能依赖当前变量的值:如Num++或者Num=Num*5这些操作。
在双重检查单例模式下使用 Volatile 来保证线程安全的原理就是利用了 Volatile 修饰的变量因为插入了内存屏障,禁止了指令重排,从而保证了有序性.即 User user = new User()这个操作,原本的是会被分解为三个操作:
(a)给Singleton的实例分配内存 (b)调用Singleton()的构造函数,初始化成员字段; (c)将singleton对象指向分配的内存空间(即singleton不为空了); 复制代码
变量加上volatile可以防止对象初始化的无序性,对象必须初始化完成后才将地址返回。