synchronized & volatile
[toc]
实现多线程的两种方式:①、继承 Thread 类;②、实现 Runnable 接口(推荐使用)。
非线程安全:多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改、值不同步的情况,进而影响程序的执行流程。
守护线程:是为其他线程的运行提供便利服务的,最典型的应用就是 GC(垃圾回收线程)。
synchronized 关键字
synchronized 取得的锁都是对象锁,拥有锁重入的功能。
锁重入:当一个线程得到一个对象锁后,再次请求此对象锁时,可以再次得到该对象的锁的。
可重入锁:比如:有一条线程获得了某个对象的锁,此时这个对象锁还没有被释放,当其想再次获取这个对象的锁时,还是可以获取的。但如果不可锁重入的话,就会出现死锁。
synchronized 方法:对当前对象进行加锁;
synchronized 代码块:对某一个对象进行加锁,默认(this);
synchronized 同步代码块
- 同步代码块比同步方法范围小。
- 同步代码块可以指定对象锁。
synchronized(非 this 对象 X)
- synchronized(非 this 对象 X) 代码块中的程序,与同步方法是异步的,不会与其他锁 this 同步方法争抢 this 锁,所以大大提高运行效率。
- 也解决"脏读"问题。
静态同步 synchronized 方法与 synchronized(Class) 代码块
- 非静态同步 synchronized 方法是给对象加锁;静态同步 synchronized 方法是给 Class 加锁的,对类的所有实例对象起作用。
- synchronized(class) 代码块与静态同步 synchronized 方法作用一样。
volatile 关键字
主要作用:使变量在多个线程之间可见。
private boolean isRunning = true; 存在于公共堆栈及线程的私有堆栈中。
volatile 的作用:强制从公共堆栈中取得变量的值,而不是从线程私有堆栈中取得
volatile 最致命的缺点:不支持原子性。
Q:volatile & synchronized 的区别?
- volatile 只能修饰变量,而 synchronized 可以修饰方法、代码块
- volatile 性能比 synchronized 好,但是使用几率不大。
- 多线程访问 volatile 不会发生阻塞,而 synchronized 会出现阻塞
- volatile 能保证数据的可见性,但不能保证原子性,而 synchronized 可以保证原子性、间接保证可见性(将私有内存和公共内存中的数据做同步)
- volatile 解决的是变量在多个线程之间的可见性,而 synchronized 解决的是多个线程之间访问资源的同步性
volatile 的非原子性
如果修改实例变量中的数据,会出现非线程安全的问题。
,比如:i++,也就是 i = i + 1 并不是一个原子操作,也就是非线程安全的。i++ 的操作步骤分解如下:
- 从内存中取出 i 值
- 计算 i 的值(此时,如果一个线程修改 i 的值,就出现脏数据)
- 将 i 的值写到内存中
Q:为什么使用 volatile 会出现非线程安全的问题?
A:变量在内存中工作的过程:
- read & load 阶段:从主存复制变量到当前线程工作内存
- use & assign 阶段:执行代码,改变共享变量值
- store & write 阶段:用工作内存数据刷新主存对应变量的值
如果在 read & load 阶段之后,变量发生修改,就会出现私有内存和公共内存中的变量不同步,从而出现非线程安全问题。
volatile 总结: 对于用 volatile 修饰的变量,JVM 虚拟机只是保证从主内存加载到线程工作内存的值是最新的。也就是说,volatile 关键字解决的是变量读时的可见性问题,但无法保证原子性,对于多个线程访问同一个实例变量还是需要加锁同步。
synchronized 代码块有 volatile 同步的功能
synchronized 可以使多个线程访问同一个资源具有同步性,而且它还具有将线程工作内存中的私有变量与公共变量同步的功能。
总结:synchronized 不仅可以解决一个线程看到对象处于不一致的状态,还可以保证进入同步方法或者同步代码块的每个线程,都看到由一个锁保护之前所有的修改效果。
网友评论