初识指令重排序,Java 中的锁
初识指令重排序,Java 中的锁
指令重排序
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(),后面就不调用系统函数了.
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
java nio消息半包、粘包解决方案
java nio消息半包、粘包解决方案 问题背景NIO是面向缓冲区进行通信的,不是面向流的。我们都知道,既然是缓冲区,那它一定存在一个固定大小。这样一来通常会遇到两个问题: 消息粘包:当缓冲区足够大,由于网络不稳定种种原因,可能会有多条消息从通道读入缓冲区,此时如果无法分清数据包之间的界限,就会导致粘包问题;消息不完整:若消息没有接收完,缓冲区就被填满了,会导致从缓冲区取出的消息不完整,即半包的现象。介绍这个问题之前,务必要提一下我代码整体架构。代码参见GitHub仓库 https://github.com/CuriousLei/smyl-im 在这个项目中,我的NIO核心库设计思路流程图如下所示 介绍: 服务端为每一个连接上的客户端建立一个Connector对象,为其提供IO服务;ioArgs对象内部实例域引用了缓冲区buffer,作为直接与channel进行数据交互的缓冲区;两个线程池,分别操控ioArgs进行读和写操作;connector与ioArgs关系:(1)输入,线程池处理读事件,数据写入ioArgs,并回调给connector;(2)输出,connector将数据写入io...
- 下一篇
xJavaFxTool 0.2.2 发布,添加全新首页
基于 JavaFx 搭建的实用小工具集合xJavaFxTool更新至 0.2.1 版本,此版本添加了全新的首页菜单,可在设置中切换。 此次更新中新增内容如下: 添加关系型数据库同步工具 添加文件生成工具 添加全新首页布局 此次更新中优化的内容如下: 优化插件管理页面(添加下载进度条等) 优化二维码生成工具页面布局 添加javafx代码生成工具生成插件模版功能 升级xcore包中相关依赖版本
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS6,CentOS7官方镜像安装Oracle11G
- SpringBoot2整合Redis,开启缓存,提高访问速度
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Hadoop3单机部署,实现最简伪集群
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果