volatile
首先我们要了解java内存模型。所有实例域、静态域、数组元素都在堆内存中,堆内存在线程之间共享。java线程之间的通信由java内存模型(JMM)控制,JMM决定一个线程的写入何时对另一个线程可见。JMM定义了线程和主存之间的抽象关系:线程之间的共享变量存住在主存中,每个线程都有一行个私有的内存,本地内存存储了该线程以读/写共享变量的副本。
如图,线程之间要实现通信的话,必须经历以下两个步骤:
- 线程A把本地内存A中更新过的共享变量刷新到主存中去。
- 线程B到主存中去读取线程A之前已经更新过的共享变量。
然而volatile关键字保证了变量的可见性
对添加volatile关键字的变量,它的汇编码中会有一行加lock前缀。lock前缀的指令在多核处理器下引发了以下两个事件:
- 将当前处理器缓存行的内容写回到内存。
- 这个写回内存的操作会使其他CPU里缓存了该内存地址的数据无效。
synchronized
volatile并不能真正保证线程安全,它只能确保一个线程修改了数据后,其他线程能够看到这个改动。但两个线程同时修改一个数据时还是会发生冲突。要从根本上解决这个问题,我们就必学保证多个线程对变量进行操作时完成同步,也就是,当线程A在写入时,线程B不仅不能写,同时也不能读。
关键字synchronized的作用是实现线程间同步。它的工作是对同步的代码加锁,使得每一次只能有一个线程进入同步块,从而保证线程间的安全性。
- 指定加锁对象:对给定对象加锁,进入同步代码前要获得给定的对象锁。
- 直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。
- 直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。
synchronized(instance){}//指定加锁对象
public synchronized void meth(){}//直接作用于实例方法,与上面加锁方式等价
(public static synchronized void meth(){}//这里是对类.class 加锁 而不是对类加锁)
举个例子
public class Demo{
String str="ddd";
//对str加锁
public void demo1(){
synchronized(str){}
}
//对this加锁
public synchronized void meth(){}
//对Demo.class加锁
public static synchronized void meth(){}
}
synchronized和volatile的
1)volatile本质是在告诉jvm当前变量在寄存器中的值是不确定的,需要从主存中读取,synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住.
2)volatile仅能使用在变量级别,synchronized则可以使用在变量,方法。
3)volatile仅能实现变量的修改可见性,但不保证原子性。而synchronized则可以保证变量的修改可见性和原子性。
4)volatile不会造成线程的阻塞,而synchronized可能会造成线程的阻塞。
5)volatile解决的是变量在多个线程之间的可见性,而synchronized解决的是多个线程之间资源访问的同步性。
synchronized和重入锁(reentrantlock)的区别
- 与synchronized相比,重入锁有着显示的操作过程。开发人员必须手动指定何时加锁,何时释放锁。
- 对于synchronized来说,如果一个线程在等待锁,,那么结果只有两种情况,要么他获得这把锁继续执行,要么保持等待。而重入锁则提供另一种可能就是线程可以被中断。
- reentrantLock提供了可以超时的锁。
- 提供了乐观锁机制,trylock不会引起线程阻塞。