原子性, 可见性与有序性
在多线程中, 线程同步的时候一般需要考虑原子性, 可见性与有序性
原子性
原子性定义: 一个操作或者多个操作在执行过程中要么全部执行完成, 要么全部都不执行, 不存在执行一部分的情况.
以我们在 Java 代码中经常用到的自增操作 i++ 为例, i++ 实际上并不是一步操作, 而是首先对 i 的值加一, 然后将结果再赋值给 i. 在单线程中不会存在问题, 但如果在多线程中我们考虑这样一个情况: i 是一个共享变量, 初始值为 0, 假设线程一以执行到某一步正好进行自增操作 i++, 刚好对 i 进行了加一但是还没将值重新赋给 i, 此时当前线程被 CPU 挂起, 而另一个线程二开始执行, 刚好也对 i 进行了一个赋值操作 i=10;, 等线程一重新执行后会将 i 自增后的值 1 赋给 i, 此时相当于覆盖了线程二的赋值操作. 此时将会产生线程不安全的情况.
可见性
多个线程同时访问一个共享的变量的时候, 每个线程的工作内存有这个变量的一个拷贝, 变量本身还是保存在共享内存 (堆) 中. 所以并不是每一次一个线程修改了值后其他线程都可以立即取到修改后的值. 可见性是指当其他的线程访问同一个变量时, 当一个线程修改了这个变量的值, 其他线程也能够立即看得到修改的值.
有序性
有序性是指程序的执行严格按照我们写的代码的顺序进行执行.
指令重排
一般情况下, CPU 和编译器为了提升程序执行的效率, 会按照一定的规则允许对指令进行优化, 即调整实际指令运行的顺序. 指令重排不会对单线程的程序造成任何不利的影响, 但是多线程环境下将会产生一些影响. 指令重排的前提条件是指令调整后不会影响单线程程序的执行:
- int i = 2; //statement 1
- int j = 1;//statement 2
- int k = i*j;//statement 3
在上面的代码中对于语句 1 和语句 2 相互之间没有任何依赖, 所以可能发生指令重排, 但是语句 3 和语句 1,2 都有关系, 所以语句 3 一定是在语句 1 和语句 2 之后执行的. 所以单线程情况下是绝对不会出现问题的. 但是对于多线程可能就发生只是初始化了语句 1 或者语句 2 就执行语句 3 了.
要保证在多线程下线程安全, 这三大性质都是必须要保证的, 而一旦其中一项无法保证那么不是线程安全的. 前面的 synchronized 关键字就是实现了这三大特性的.
volatile
虽然已经有了 synchronized 关键字保证了线程安全需要的三大特性, 但是在 JDK1.8 优化 synchronized 之前, synchronized 关键字都是一个重量级的锁, 对程序的效率有着比较大的影响. 在 java 中还有一个 synchronized 关键字的轻量级的实现 - volatile 关键字. volatile 关键字是在 JDK1.5 之后重新被重用的一个关键字, 它可以保证上诉三大特性中的有序性和可见性, 但是不能保证原子性, 所以它实际上是线程不安全的.
保证可见性
出现可见性的原因在于私有栈帧中的值和公共堆中的共享值不同得问题.
当一个线程在修改普通变量时, 其他线程不能立刻看到修改后的值, 如果此时有其他线程读取该变量的值, 实际上读到的是没有修改的值.
volatile 关键字作用在于当要使用时强制从主内存中读取值, 保证每次读取的都是公共内存中的值.
防止指令重排
内存屏障也称为内存栅栏或栅栏指令, 是一种屏障指令, 它使 CPU 或编译器对屏障指令之前和之后发出的内存操作执行一个排序约束. 这通常意味着在屏障之前发布的操作被保证在屏障之后发布的操作之前执行.
volatile 关键字功能的实现既是通过内存屏障完成的, 当使用 volatile 关键字修饰的变量进行读写是便会加上内存屏障来保证设计变量的操作顺序执行, 需要注意的是其和 synchronized 关键字的同步是不一样的.
为什么不是原子性的?
实际上 volatile 关键字保证的事所有线程从主存中取到的值是最新的, 但是多个线程修改了改变量的值并不会通知其他线程, 除非其他线程再次从主存中取值.
在以上阶段中, 比如存在两个线程且两个线程都已经加载了变量 count 的值, 这时线程一将 count 修改为 10, 线程二将值修改为 20, 但是两个线程之间并不知道对方都改了值, 而最终写到主存的值也是后写入的那一个, 即始终都一个线程修改的值被覆盖, 所以其并不是原子性的. 在涉及到多线程操作共享变量是还是应该加锁进行操作.
synchronized 和 volatile 关键字的比较
volatile 关键字并不是同步操作, 其在多线程访问下不会进行阻塞, 而 synchronized 关键字会发阻塞.
volatile 关键字能保证有序性和可见性, 但不能保证原子性. synchronized 三种特性都能保证, 所以 synchronized 是线程同步的, 而 volatile 不是.
volatile 是线程同步的轻量级实现, 所以效率较之 synchronized 要高.
等待通知机制
在多线程程序中可能会存在多个程序相互配合完成一项功能, 这是就需要线程之间进行通信, 在一个线程的工作完成后通知后续线程工作. 通常情况下我们可以在一个线程中进行一个 while 循环操作, 设置一个标志 flag, 当该线程的前置线程完成后修改 flag, 后面的 while 得到这个标志后知道自身需要开始工作了, 跳出循环. 但是这种方法的劣势在于 while 循环使得该线程一个需要处于运行中, 同时当多个线程相互之间都需要进行通信时会使得程序变得极其复杂. 为了解决这个问题, 有人提出了一种等待通知机制.
等待通知机制是利用 JDK 中提供的 API 中的 wait()和 notify/notifyAll()方法来进行实现 (实际上 Lock 类中的方法也能实现),wait() 方法是使得当前线程进入等待队列中, notify/notifyAll()是将等待的线程唤醒.
等待方
获取对象锁
如果条件不满足, 调用对象的 wait 方法, 被通知后依然要检查条件是否满足
条件满足以后, 才能执行相关的业务逻辑
- Synchronized(对象){
- While(条件不满足){
对象. wait()
- }
- // do your working
- }
通知方
获得对象的锁;
改变条件;
通知所有等待在对象的线程
Synchronized(对象){
业务逻辑处理, 改变条件
对象. notify/notifyAll
}
实例
- public class User {
- private int age = 30;
- public int getAge() {
- return age;
- }
- public void setAge(int age) {
- this.age = age;
- }
- /**
- * 1, 获取对象锁
- * 2, 如果条件不满足, 调用对象的 wait 方法, 被通知后依然要检查条件是否满足
- * 3, 条件满足以后, 才能执行相关的业务逻辑
- */
- public synchronized void waitAge(){
- System.out.println("age is" + this.age);
- while(this.age>= 20){
- // 条件不满足
- try {
- System.out.println("current thread is waiting");
- wait();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- // 满足条件后执行
- System.out.println("current thread" + Thread.currentThread().getName() + "age is" + this.age);
- }
- /**
- * 1, 获得对象的锁;
- * 2, 改变条件;
- * 3, 通知所有等待在对象的线程
- */
- public synchronized void changeAge(){
- // 修改条件
- this.age = new Random().nextInt(20);
- System.out.println("inform all thread");
- // 这里使用 notifyAll()是因为 notify()方法无法指定唤醒某一个线程, notify()的唤醒是随机的
- //notifyAll()唤醒所有等待线程
- notifyAll();
- }
- }
测试类:
- public class WaitAndInform extends Thread{
- private static User user = new User();
- @Override
- public void run() {
- user.waitAge();
- }
- public static void main(String[] args) throws InterruptedException {
- for(int i=0;i<=4;i++){
- new WaitAndInform().start();
- }
- Thread.sleep(1000);
- // 修改条件 唤醒其他线程
- user.changeAge();
- }
- }
来源: https://www.cnblogs.com/liyus/p/10015953.html