指令重排序
Java 语言规范 JVM 线程内部维持顺序化语义, 即只要程序的最终结果与它顺序化情况的结果相等, 那么指令的执行顺序可以与代码逻辑顺序不一致, 这个过程就叫做指令的重排序.
指令重排序的意义: 使指令更加符合 CPU 的执行特性, 最大限度的发挥机器的性能, 提高程序的执行效率.
看个 demo
- public static void main(String[] args) throws InterruptedException {
- int j=0;
- int k=0;
- j++;
- System.out.println(k);
- System.out.println(j);
- }
上面这段代码可能会被重排序: 如下
- public static void main(String[] args) throws InterruptedException {
- int k=0;
- System.out.println(k);
- int j=0;
- j++;
- System.out.println(j);
- }
此时指令的执行顺序可以与代码逻辑顺序不一致, 但不影响程序的最终结果.
再看个 demo
- public class ThreadExample2 {
- static int i;
- public static boolean runing = true;
- public static void main(String[] args) throws InterruptedException {
- traditional();
- Thread.sleep(100);
- runing = false;
- }
- public static void traditional() {
- Thread thread = new Thread() {
- @Override
- public void run() {
- while (runing){
- i++;// 没有方法, JVM 会做指令重排序, 激进优化
- }
- }
- };
- thread.start();
- }
- }
执行下 main 方法
可以看出该程序一直在跑, 不会停止.
此时 jvm 发现 traditional 方法内没有其他方法, JVM 会做指令重排序, 采取激进优化策略, 对我们的代码进行了重排序
如下:
- static int i;
- public static boolean runing = true;
- public static void main(String[] args) throws InterruptedException {
- traditional();
- Thread.sleep(100);
- runing = false;
- }
- public static void traditional() {
- Thread thread = new Thread() {
- boolean temp=runing;// 注意这里, 此时 while 的条件永远为 true
- @Override
- public void run() {
- while (temp){
- i++;// 没有方法, JVM 会做指令重排序, 激进优化
- }
- }
- };
- thread.start();
- }
因此程序不会停止.
我们稍微改动下代码, 在 while 循环里加个方法
- static int i;
- public static boolean runing = true;
- public static void main(String[] args) throws InterruptedException {
- traditional();
- Thread.sleep(100);
- runing = false;
- }
- public static void traditional() {
- boolean temp=runing;
- Thread thread = new Thread() {
- @Override
- public void run() {
- while (runing){//
- i++;// 没有方法, JVM 会做指令重排序, 激进优化
- // 有方法, JVM 认为可能存在方法溢出, 不做指令重排序, 保守优化策略
- aa();
- }
- }
- };
- thread.start();
- }
- public static void aa(){
- System.out.println("hello");
- }
看下结果
可以看出, 程序自行停止了, 因为有方法, JVM 认为可能存在方法溢出, 不做指令重排序, 采取保守优化策略
runing = false;
全局变量 runing 改动值以后, 被 thread 线程识别, while 循环里值变为 false, 就自动停止了.
ok, 继续, 我们把 main 方法中的 sleep() 注释掉, 如下
- public static void main(String[] args) throws InterruptedException {
- traditional();
- //Thread.sleep(100);
- runing = false;// 会优先执行主线程的代码
- }
- public static void traditional() {
- boolean temp=runing;
- Thread thread = new Thread() {
- @Override
- public void run() {
- while (runing){//
- i++;
- }
- }
- };
- thread.start();
- }
看下结果:
此时, 程序停止了, 这是为什么呢:
可能是因为 thread 线程和 main 线程竞争 CPU 资源的时候, 会优先分配给 main 线程 (我不确定, 读者们可以自己思考一下)
Java 中的锁
synchronized 关键字
在 1.6 版本之前, synchronized 都是重量级锁
1.6 之后, synchronized 被优化, 因为互斥锁比较笨重, 如果线程没有互斥, 那就不需要互斥锁
重量级锁
1. 当一个线程要访问一个共享变量时, 先用锁把变量锁住, 然后再操作, 操作完了之后再释放掉锁, 完成
2. 当另一个线程也要访问这个变量时, 发现这个变量被锁住了, 无法访问, 它就会一直等待, 直到锁没了, 它再给这个变量上个锁, 然后使用, 使用完了释放锁, 以此进行
3. 我们可以这么理解: 重量级锁是调用操作系统的函数来实现的锁 --mutex-- 互斥锁
以 Linux 为例:
互斥变量使用特定的数据类型: pthread_mutex_t 结构体, 可以认为这是一个函数
可以用 pthread_mutex_init 进行函数动态的创建 : int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t * attr)
对锁的操作主要包括加锁 pthread_mutex_lock(), 解锁 pthread_mutex_unlock() 和测试加锁 pthread_mutex_trylock() 三个
3.1 int pthread_mutex_tlock(pthread_mutex_t *mutex) 在寄存器中对变量操作 (加 / 减 1)
3.2 int pthread_mutex_unlock(pthread_mutex_t *mutex) 释放锁, 状态恢复
3.3 int pthread_mutex_trylock(pthread_mutex_t *mutex)
pthread_mutex_trylock() 语义与 pthread_mutex_lock() 类似, 不同的是在锁已经被占据时返回 EBUSY 而不是挂起等待
函数 pthread_mutex_trylock 会尝试对互斥量加锁, 如果该互斥量已经被锁住, 函数调用失败, 返回 EBUSY, 否则加锁成功返回 0, 线程不会被阻塞
偏向锁
偏向锁是 synchronized 锁的对象没有资源竞争的情况下存在的, 不会一直调用操作系统函数实现 (第一次会调用), 而重量级锁每次都会调用
看个 demo
- public class SyncDemo2 {
- Object o= new Object();
- public static void main(String[] args) {
- System.out.println("pppppppppppppppppppppp");
- SyncDemo2 syncDemo = new SyncDemo2();
- syncDemo.start();
- }
- public void start() {
- Thread thread = new Thread() {
- public void run() {
- while (true) {
- try {
- Thread.sleep(500);
- sync();
- } catch (InterruptedException e) {
- }
- }
- }
- };
- Thread thread2 = new Thread() {
- @Override
- public void run() {
- while (true) {
- try {
- Thread.sleep(500);
- sync();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- };
- thread.setName("t1");
- thread2.setName("t2");
- // 两个线程竞争时, synchronized 是重量级锁, 一个线程时, synchronized 是偏向锁
- thread.start();
- thread2.start();
- }
- // 在 1.6 版本之前, synchronized 都是重量级锁
- //1.6 之后, synchronized 被优化, 因为互斥锁比较笨重, 如果线程没有互斥, 那就不需要互斥锁
- public void sync() {
- synchronized (o) {
- System.out.println(Thread.currentThread().getName());
- }
- }
- }
代码很简单, 就是启动两个线程, 并且调用同一个同步方法, 看下结果
可以看到, 两个线程都执行了该同步方法, 此时两个线程竞争, synchronized 是重量级锁
我们把一个线程注释掉
- // 两个线程竞争时, synchronized 是重量级锁, 一个线程时, synchronized 是偏向锁
- thread.start();
- //thread2.start();
看下结果:
此时 synchronized 是偏向锁
那么怎么证明呢: 我目前没那个实力, 给个思路.
1. 需要编译并修改 Linux 源码函数 pthread_mutex_lock(), 在函数中打印当前线程的 pid
2. 在同步方法中打印语句 "current id"+ 当前 pid(需要自己写 c 语言实现),java 的 Thread.currentThread().getId() 不能获取操作系统级别的 pid
3. 两个线程竞争时, 执行一次
说明是重量级锁, 因为每次都调用操作系统的函数 pthread_mutex_lock() 来实现
4. 注释掉一个线程, 再执行一次
说明是偏向锁, 因为第一次会调用 pthread_mutex_lock(), 后面就不调用系统函数了.
posted on 2020-04-19 16:36 路仁甲 阅读 (...) 评论 (...) 编辑 收藏
来源: https://www.cnblogs.com/lusaisai/p/12731593.html