高性能编程——线程安全问题之Java锁相关(Synchronized深度解析)
内容导读
互联网集市收集整理的这篇技术教程文章主要介绍了高性能编程——线程安全问题之Java锁相关(Synchronized深度解析),小编现在分享给大家,供广大互联网技能从业者学习和参考。文章包含4788字,纯文字阅读大概需要7分钟。
内容图文
文章目录
Java中锁的概念
其实在上一章原子性的讲解中已经提到并写过一个锁了,但是这还远远不够,Java中关于锁还是有很多东西需要学习,这里先介绍几个与锁相关的概念。
自旋锁
指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。
上图就是一个典型的场景,循环获取锁的过程就像是在旋转一样。
乐观锁
假定没有冲突,在修改数据时如果发现数据和之前获取的不一致,则读最新数据,修改后重试修改。也就是说读取的时候以及其他时候都不加锁,只有在修改前才加锁。
悲观锁
假定会发生并发冲突,同步所有对数据的相关操作,从读数据就开始上锁。也就是说从读取数据开始就加锁了。
独享锁(写锁)
给资源加上写锁,线程可以修改资源,其他线程不能再加锁;(单写)
共享锁(读锁)
给资源加上读锁后只能读不能改,其他线程也只能加读锁,不能加写锁;(多读)
可重入锁、不可重入锁
所谓可重入值的是线程拿到一把锁之后,可以自由进入由同一把锁所同步的其他代码。
公平锁、非公平锁
抢锁是有顺序的,保证抢到锁的顺序就是抢锁的顺序这就是公平锁。
同步关键字synchronized
- 用于实例方法、静态方法时,隐式指定锁对象
- 用于代码块时,显示指定锁对象
- 锁的作用域:对象锁、类锁、分布式锁
- 引申:如果是多个进程,怎么办?
认识synchronized
class Counter{
private static int i=0;
//synchronized(this){}
public synchronized void update(){
//访问数据库
}
public void updateBlock(){
synchronized (this){
//访问数据库
}
}
//synchronized(Counter.class)
public static synchronized void staticUpdate(){
//访问数据库
}
public static void staticUpdateBlock(){
synchronized(Counter.class){
//访问数据库
}
}
}
从上面的伪代码简述了synchronized的用法,我们需要明确一点那就是synchronized是一个加锁的过程,但是锁并不是this或者Counter.class,而是JVM内部经过一系列的处理得出来的。
synchronized的特性
synchronized是一个可重入、独享、悲观锁。它还有锁消除、锁粗化等锁优化功能。
锁消除
锁消除这个概念就是在特定的情况下,可以把锁消除了的意思,该过程发生在JIT即时编译的时候。来看一段代码:
public void test1(Object arg){
//StringBuilder线程不安全,StringBuffer用了synchronized,是线程安全的
// jit 优化,消除了锁
StringBuffer stringBuffer = new StringBuffer(); //局部变量,没有在其他线程中使用
stringBuffer.append("a");
stringBuffer.append("b");
stringBuffer.append("c");
stringBuffer.append("a");
stringBuffer.append("b");
stringBuffer.append("c");
stringBuffer.append("a");
stringBuffer.append("b");
stringBuffer.append("c");
// System.out.println(stringBuffer.toString());;
}
因为上述代码刚开始执行的时候还会循规蹈矩的执行,但是当该方法执行多次的时候就会触发jit编译,到时候就会消除掉synchronized关键字了,这就是锁消除。但是要注意,这种情况只存在于单线程情况。
锁粗化
用于提高锁的细粒度,把很多细粒度很低的代码放在一个synchronized之中,提高性能。举个例子:
上面这些小操作最后会被JVM优化成下面这个框的样子。
synchronized原理学习
想要明白它的原理就必须先去知道这个锁是如何锁住this这个对象的,它的加锁的状态是如何记录的?状态会被记录到this对象中吗?如果锁被占用了,那么申请锁的线程将会被挂起,当释放锁的时候,又会唤醒挂起线程队列中的头队列。想要了解上面的机制,就必须先去深入学习对象相关的知识:
Java对象存储原理
先来看一段代码以及Java的内存模型:
首先int a = 1;基本类型的局部变量会直接存储在main线程的局部变量表,引用james也会存在该局部变量表。
而类的成员变量都会存在堆内存中该对象之中:
静态变量、方法(静态和非静态)都存储在方法区中,而对象想取到自己想要的信息就要靠对象头去指向方法区中的类。就能获得初始变量和方法了。要注意在该对象中连引用都是存储的,直接指向另一个对象,而对象再去通过对象头找到指定的类:
对象头详解
我们抢锁需要修改一个标记,这个标记其实就存储在对象头之中,我们先来看一个对象头的详解图:
Mark Word
关于锁的标记都存储在这个Mark Word(标记字段)之中。它本质上就是一段堆内存中的内存区域。大小为64位(32位机器为32位)。
上面是Mark Word的5种状态。
如果Mark Word像第一种那就是没有加上锁。第二段则是偏向锁。第三段为轻量级锁。第四段则是重量级锁。
锁的升级过程
偏向锁
偏向锁其实就是把0改成thread ID,其实就是加锁之后不再解锁,针对只有对一个线程有用的场景,出现过之后就没有用了。可以在JVM中用参数-XX: -UseBiasedLocking来禁用偏置锁定。
偏向锁,本质就是无锁,如果没有发生过任何多线程争抢锁的情况,JVM认为就是单线程,无需做同步
轻量级锁
加锁的时候首先会把Mark Word复制到有同步代码块的栈帧之中,然后所有线程申请CAS操作,抢到锁的线程把自己的Lock record address写入,这样其他线程的CAS就无法成功了,没有成功的线程则自旋,自选到一定程度就会锁升级。
重量级锁
如果轻量级锁会一直自旋去申请锁,消耗CPU性能,所以升级为重量级锁,就是把申请锁的线程挂起存储在一个entryList,当解锁的时候再去唤醒。
Class meta address
是指方法区中类的地址,在对象初始化的时候用到,相当于一个指针。
Array Length
数组长度,就是假如这个对象是个数组对象的话,记录它的长度。
段落总是简写 发布了38 篇原创文章 · 获赞 10 · 访问量 1130 私信 关注内容总结
以上是互联网集市为您收集整理的高性能编程——线程安全问题之Java锁相关(Synchronized深度解析)全部内容,希望文章能够帮你解决高性能编程——线程安全问题之Java锁相关(Synchronized深度解析)所遇到的程序开发问题。 如果觉得互联网集市技术教程内容还不错,欢迎将互联网集市网站推荐给程序员好友。
内容备注
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 gblab@vip.qq.com 举报,一经查实,本站将立刻删除。
内容手机端
扫描二维码推送至手机访问。