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

指令重排序

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. iOS开发:Swift/Objective-C高效生成随机字符串

    原文连接 Objective-C版 // 随机生成字符串(由大小写字母.数字组成) + (NSString *)random: (int)len { char ch[len]; for (int in ...

  2. Linux下安装Python3.4

    PS:如果本机安装了python2,尽量不要管他,使用python3运行python脚本就好,因为可能有程序依赖目前的python2环境, 比如yum!!!!! 不要动现有的python2环境! 1. ...

  3. Cisco 综合配置(二)

    要求: 1. PC1 属于VLAN10,PC2属于VLAN20,网关:Master Router2. VLAN10.20 的网段为:192.168.10.0/24 . 192.168.20.0/24 ...

  4. Django模板层学习笔记

    一. 前言 Django模板层的知识包括标签.过滤器.自定义标签.自定义过滤器以及inclusion_tag,最重要的是模板的继承和导入. 首先模板层最重要的是模板语法,之前我们提过涉及到变量用模板语 ...

  5. hdu1856 并查集

    题目链接:http://icpc.njust.edu.cn/Problem/Hdu/1856/ 题目就是要求并查集中各树的大小的最大值,我们只要在根节点处存树的大小就可以,合并也是合并根节点的数,最后 ...

  6. 用Arcgis缓存文件发布服务

    切片文件类型为: 其中_alllayers中为各等级的切片文件,xml文件为切片信息(级别).发布服务的步骤如下: 1.将文件拷贝到服务器上: 2.将gdb和xml文件拷贝出来,放到桌面上: 3.打开 ...

  7. 基于zookeeper实现分布式锁和基于redis实现分布所的区别

    1,实现方式不同 zookeeper实现分布式锁:通过创建一个临时节点,创建的成功节点的服务则抢占到分布式锁,可做业务逻辑.当业务逻辑完成,连接中断,节点消失,继续下一轮的锁的抢占. redis实现分 ...

  8. 意外发现PHP另一个显示转换类型 binary

    竟然不知道除了(string)$a之外,还有(binary)$a知道unset可以不加括号,但不知道还有这种写法(unset) 请看lex文件(php-7.1.8) <ST_IN_SCRIPTI ...

  9. iOS 架构

    一.MVC MVC 全名 Model View Controller,是模型(model)-视图(view)-控制器(controller)的缩写,一种软件设计典范,用一种业务逻辑.数据.界面显示分离 ...

  10. Bootstrap Blazor 组件库

    项目介绍 Blazor 是一个使用 .NET 生成交互式客户端 Web UI 的框架: 使用 C# 代替 JavaScript 来创建丰富的交互式 UI. 共享使用 .NET 编写的服务器端和客户端应 ...