Java的重排序影响线程调用顺序
来源:互联网 发布:淘宝店铺名称怎么改 编辑:程序博客网 时间:2024/06/15 15:06
你所认为的程序运行顺序是什么样的呢?是JVM照着程序编写的顺序运行吗?
正确答案是:不是的。
今天在学习多线程的时候遇到了一个问题。代码本身的功能是为了验证synchronized关键字对于线程锁的作用。用synchronized锁住Example类的非静态方法,可以让两个线程不同步执行两个方法。代码如下:
/** * Created by K Lin on 2017/7/10. */public class Demo1 { public static void main(String[] args) { Example example = new Example(); Thread t1 = new TheThread(example); Thread t2 = new TheThread2(example); t1.start(); t2.start(); }}class Example { public synchronized void execute() { for (int i = 0; i < 5; i++) { try { Thread.sleep((long) (Math.random() * 1000)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("lin: " + i); } } public synchronized void execute2() { for (int i = 0; i < 5; i++) { try { Thread.sleep((long) (Math.random() * 1000)); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("kai: " + i); } }}class TheThread extends Thread { private Example example; public TheThread(Example example) { this.example = example; } @Override public void run() { this.example.execute(); }}class TheThread2 extends Thread { private Example example; public TheThread2(Example example) { this.example = example; } @Override public void run() { this.example.execute2(); }}
预期的输出是这个样子的:
按程序编写的先后顺序进行执行,这是意想得到的。但是,其中一次运行出现的结果却令我百思不得其解:
为什么会出现这个情况呢?Google了半天的我知道了有指令重排序这种东西。也知道了happen-before原则。
重排序在多线程环境下出现的概率是很高的。在关键字上有volatile和synchronized可以禁用重排序,除此之外还有一些规则,也正是这些规则,使得我们在平时的编程工作中没有感受到重排序的坏处。
程序次序规则(Program Order Rule):在一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作。准确地说应该是控制流顺序而不是代码顺序,因为要考虑分支、循环等结构。
监视器锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面对同一个对象锁的lock操作。这里强调的是同一个锁,而“后面”指的是时间上的先后顺序,如发生在其他线程中的lock操作。
volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作发生于后面对这个变量的读操作,这里的“后面”也指的是时间上的先后顺序。
线程启动规则(Thread Start Rule):Thread独享的start()方法先行于此线程的每一个动作。
线程终止规则(Thread Termination Rule):线程中的每个操作都先行发生于对此线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值检测到线程已经终止执行。
线程中断规则(Thread Interruption Rule):对线程interrupte()方法的调用优先于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测线程是否已中断。
对象终结原则(Finalizer Rule):一个对象的初始化完成(构造函数执行结束)先行发生于它的finalize()方法的开始。
传递性(Transitivity):如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论。
正是以上这些规则保障了happen-before的顺序,如果不符合以上规则,那么在多线程环境下就不能保证执行顺序等同于代码顺序,也就是“如果在本线程中观察,所有的操作都是有序的;如果在一个线程中观察另外一个线程,则不符合以上规则的都是无序的”
为了遵守上面的规范,我在t1.start();
语句后添加了如下代码:
try{ t1.join(); } catch (InterruptedException e) { e.printStackTrace(); }
即符合了线程终止规则。(其实还有多种方法)在之后的不下50次的程序运行中都没有再出现错误结果。
但是,在继续学习之后,我又发现,其实指令重排序并不是影响这个程序运行结果的关键,关键原因竟是:线程启动过程不具备原子性,且进程的执行是随机的!。这会导致一个问题,当t1线程处于启动阶段,它有可能在线程启动准备完后,被t2线程挤掉,停止执行,而t2线程启动完成之后,才来继续完成t1的线程执行操作。这才是结果输出顺序不同的原因!
而前面所做的修改方法依旧是正确的,因为它确保了t2的执行必须处于t1完成之后。所以不会再发生顺序颠倒的情况!
因此,遵守好happen-before原则以及运用好synchronized、Lock以及volatile修饰符在多线程编程中是十分之重要的!
- Java的重排序影响线程调用顺序
- Java重排序对多线程的影响
- Java多线程--重排序与顺序一致性
- java类初始化顺序的影响
- java并发编程的艺术【三】-【二】重排序与顺序一致性
- Java 线程的执行顺序
- GCD关于队列和函数对于调用线程的影响
- 三、Java内存模型---重排序和顺序一致性
- java并发编程学习(六) 重排序和顺序一致性
- Java 中的初始化顺序的影响和“overloading”与“overwrite”
- Java线程池的调用
- Java线程池的调用
- Java线程调用的随机性
- java的线程安全对于不同变量的影响
- CentOS影响java的线程数的因素
- Java--线程的先后执行顺序控制
- java线程不同优先级的运行顺序
- java控制线程的执行顺序
- Android使用echarts
- Python RE模块中search()和match()的区别
- 网站被挂黑链怎么办?
- 修改input file文件上传的默认样式 兼容ie8
- 强制类型转换
- Java的重排序影响线程调用顺序
- ArcGIS水文分析实战教程(10)河流平均比降计算
- redis 的启动方式
- css选择器
- UnityShader入门精要学习笔记(十六):纹理动画
- tomcat 8.5.16踩坑篇
- EbNo(EbN0)和SNR
- 选择图片或拍照设置图片
- android GridView 在TV上解决item放大时候,被其他item遮挡,单纯使用bringToFront无法解决的问题