本文是作者原创,版权归作者所有.若要转载,请注明出处.本文只贴我觉得比较重要的源码

指令重排序

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为例:

1.互斥变量使用特定的数据类型:pthread_mutex_t结构体,可以认为这是一个函数
2.可以用pthread_mutex_init进行函数动态的创建 : int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t * attr)
3.对锁的操作主要包括加锁 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(),后面就不调用系统函数了.

初识指令重排序,Java 中的锁的更多相关文章

  1. 深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则

    转: http://www.blogjava.net/xylz/archive/2010/07/03/325168.html 在这个小结里面重点讨论原子操作的原理和设计思想. 由于在下一个章节中会谈到 ...

  2. 深入浅出 Java Concurrency (4): 原子操作 part 3 指令重排序与happens-before法则[转]

    在这个小结里面重点讨论原子操作的原理和设计思想. 由于在下一个章节中会谈到锁机制,因此此小节中会适当引入锁的概念. 在Java Concurrency in Practice中是这样定义线程安全的: ...

  3. Java的多线程机制系列:不得不提的volatile及指令重排序(happen-before)

    一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专 ...

  4. Java的多线程机制系列:(四)不得不提的volatile及指令重排序(happen-before)

    一.不得不提的volatile volatile是个很老的关键字,几乎伴随着JDK的诞生而诞生,我们都知道这个关键字,但又不太清楚什么时候会使用它:我们在JDK及开源框架中随处可见这个关键字,但并发专 ...

  5. 深入浅出Java并发包—指令重排序

    前面大致提到了JDK中的一些个原子类,也提到原子类是并发的基础,更提到所谓的线程安全,其实这些类或者并发包中的这么一些类,都是为了保证系统在运行时是线程安全的,那到底怎么样才算是线程安全呢? Java ...

  6. java指令重排序的问题

    转载自于:http://my.oschina.net/004/blog/222069?fromerr=ER2mp62C 指令重排序是个比较复杂.觉得有些不可思议的问题,同样是先以例子开头(建议大家跑下 ...

  7. 【java多线程系列】java内存模型与指令重排序

    在多线程编程中,需要处理两个最核心的问题,线程之间如何通信及线程之间如何同步,线程之间通信指的是线程之间通过何种机制交换信息,同步指的是如何控制不同线程之间操作发生的相对顺序.很多读者可能会说这还不简 ...

  8. java高并发核心要点|系列4|CPU内存指令重排序(Memory Reordering)

    今天,我们来学习另一个重要的概念. CPU内存指令重排序(Memory Reordering) 什么叫重排序? 重排序的背景 我们知道现代CPU的主频越来越高,与cache的交互次数也越来越多.当CP ...

  9. java并发学习--第九章 指令重排序

    一.happns-before happns-before是学习指令重排序前的一个必须了解的知识点,他的作用主要是就是用来判断代码的执行顺序. 1.定义 happens-before是用来指定两个操作 ...

随机推荐

  1. 妈妈再也不担心我面试被Redis问得脸都绿了

    长文前排提醒,收藏向前排提醒,素质三连 (转发 + 在看 + 留言) 前排提醒! 前言 Redis 作为一个开源的,高级的键值存储和一个适用的解决方案,已经越来越在构建 「高性能」.「可扩展」 的 W ...

  2. drf 权限认证

    目录 复习 前期准备 三大认证简介 AbstracUser源码分析 自定义User下的权限六表 models.py 到settings.py中注册 注意点: 执行数据迁移的俩条命令 创建超级用户 t_ ...

  3. Python第五章-内置数据结构04-字典

    Python 内置的数据结构 四.字典(dict) 字典也是 python 提供给我们的又一个非常重要且有用的数据结构. 字典在别的语言中有时叫关联数组.关联内存.Map等. 字典中存储的是一系列的k ...

  4. PyTorch Hub发布!一行代码调用最潮模型,图灵奖得主强推

    为了调用各种经典机器学习模型,今后你不必重复造轮子了. 刚刚,Facebook宣布推出PyTorch Hub,一个包含计算机视觉.自然语言处理领域的诸多经典模型的聚合中心,让你调用起来更方便. 有多方 ...

  5. 如何利用python实现为每行添加行数编号

    可能还有更好的方法,在这里我是这么写的,针对小文件可以,但是如果文件内容太多,这种方法感觉不太好 先把所有的数据读取出来,然后利用W覆盖写入模式打开文件进行写入 遍历枚举类型数据后,默认是从0开始,然 ...

  6. 数据挖掘入门系列教程(八)之使用神经网络(基于pybrain)识别数字手写集MNIST

    目录 数据挖掘入门系列教程(八)之使用神经网络(基于pybrain)识别数字手写集MNIST 下载数据集 加载数据集 构建神经网络 反向传播(BP)算法 进行预测 F1验证 总结 参考 数据挖掘入门系 ...

  7. Material Design 组件之 AppBarLayout

    AppBarLayout 是一个垂直方向的 LinearLayout,它实现了许多符合 Material Design 设计规范的状态栏应该具有的功能,比如滚动手势. AppBarLayout 一般直 ...

  8. Pytest系列(4) - fixture的详细使用

    如果你还想从头学起Pytest,可以看看这个系列的文章哦! https://www.cnblogs.com/poloyy/category/1690628.html 前言 前面一篇讲了setup.te ...

  9. 【WPF学习】第六十五章 创建无外观控件

    用户控件的目标是提供增补控件模板的设计表面,提供一种定义控件的快速方法,代价是失去了将来的灵活性.如果喜欢用户控件的功能,但需要修改使其可视化外观,使用这种方法就有问题了.例如,设想希望使用相同的颜色 ...

  10. Step by Step!教你如何在k3s集群上使用Traefik 2.x

    本文来自边缘计算k3s社区 作者简介 Cello Spring,瑞士人.从电子起步,拥有电子工程学位.尔后开始关注计算机领域,在软件开发领域拥有多年的工作经验. Traefik是一个十分可靠的云原生动 ...