- 使用 Spring Initializr 创建 Spring Boot 应用程序
- 在Spring Boot中配置Cassandra
- 在 Spring Boot 上配置 Tomcat 连接池
- 将Camel消息路由到嵌入WildFly的Artemis上
并发编程系列之Synchronized实现原理
下面给出一个简单例子,synchronized
关键字加在两个方法上,另外一个加在方法里
public class SynchroinzedDemo {
static int a;
public static synchronized void add1(int b){
a += b;
}
public synchronized void add2(int b){
a += b;
}
public void add3(int b){
synchronized (this){
a += b;
}
}
public static void main(String[] args) {
}
}
先用使用javac编译为class文件,或者在IDE直接运行就行,找到对应class文件,使用如下命令:
javap -verbose SynchroinzedDemo.class > log.txt
找到log.txt文件,对比两个加了synchronized
关键字的方法,都有ACC_SYNCHRONIZED
这个标识
public static synchronized void add1(int);
descriptor: (I)V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #2 // Field a:I
3: iload_0
4: iadd
5: putstatic #2 // Field a:I
8: return
LineNumberTable:
line 6: 0
line 7: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 b I
public synchronized void add2(int);
descriptor: (I)V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=2, args_size=2
0: getstatic #2 // Field a:I
3: iload_1
4: iadd
5: putstatic #2 // Field a:I
8: return
LineNumberTable:
line 10: 0
line 11: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/example/concurrent/sync/SynchroinzedDemo;
0 9 1 b I
找到比较关键的monitorenter
和monitorexit
关键字,monitorenter
和monitorexit
关键字是什么?后面再介绍
public void add3(int);
descriptor: (I)V
flags: ACC_PUBLIC
Code:
stack=2, locals=4, args_size=2
0: aload_0
1: dup
2: astore_2
3: monitorenter
4: getstatic #2 // Field a:I
7: iload_1
8: iadd
9: putstatic #2 // Field a:I
12: aload_2
13: monitorexit
14: goto 22
17: astore_3
18: aload_2
19: monitorexit
20: aload_3
21: athrow
22: return
Exception table:
from to target type
4 14 17 any
17 20 17 any
LineNumberTable:
line 14: 0
line 15: 4
line 16: 12
line 17: 22
LocalVariableTable:
Start Length Slot Name Signature
0 23 0 this Lcom/example/concurrent/sync/SynchroinzedDemo;
0 23 1 b I
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 17
locals = [ class com/example/concurrent/sync/SynchroinzedDemo, int, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
综上:
synchronized
,是在ACC_SYNCHRONIZED
关键字synchronized(obj)
,对应字节码是monitorenter
、monitorexit
前面的javap编译,我们知道了monitorenter
和monitorexit
,synchronized重量级锁实现依赖于Monitor,所以需要介绍一下说明是Monitor,翻译过来是监视器?我们知道synchronized锁的作用和ReentrantLock的作用是一致的,所以synchronized实现同步的原理是否应该一样?实现上应该也要有互斥量,有等待队列,有重入计数。
前面的学习,我们知道synchronized锁的实现依赖于jvm,要实现锁就要有互斥量,jvm实现锁的方式是什么?
jvm也是程序,因为作为java程序和操作系统的中间件,所以可以直接使用操作系统提供的线程同步原语:mutex互斥量和semaphore信号量,当然也可以使用CAS锁
而jvm使用的Monitor又是什么?和jvm以及操作系统底层的线程同步原语又有什么关系?
Monitor,翻译过来可以说是官程,或者说是监视器
在使用操作系统底层的线程同步原语,需要程序员非常小心地控制mutex的down和up操作,否则很容易引起死锁等问题。为了更容易写出正确程序,所以在mutex和semaphore的基础上,提出了更高层次的同步原语monitor,当然monitor并不是操作系统提供的,而是由编译器,比如java的jvm自己去实现的,所以要使用monitor要确定编程语言是否支持,比如c语言就不支持,java语言支持,因为jvm已经实现了,所以说synchronized是jvm层面的锁
jvm如何实现monitor的?可以去github下载openJdk的源码,路径:
openjdk\hotspot\src\share\vm\runtime\objectMonitor.hpp
penjdk\hotspot\src\share\vm\runtime\objectMonitor.cpp
主要类是ObjectMonitor.cpp
,看了源码实现:
//
// The ObjectMonitor class is used to implement JavaMonitors which have
// transformed from the lightweight structure of the thread stack to a
// heavy weight lock due to contention
// objectMonitor用于实现javaMonitor,javaMonitor是由于线程争用而从线程堆栈的轻量级结构转换为的重量级锁
// It is also used as RawMonitor by the JVMTI
// initialize the monitor, exception the semaphore, all other fields
// are simple integers or pointers
ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0, // 等待中的线程数
_recursions = 0; //线程重入次数
_object = NULL; // 存储该monitor的对象
_owner = NULL; //拥有该monitor的线程
_WaitSet = NULL; //等待线程组成的双向循环链表,_WaitSet 指向第一个节点
_WaitSetLock = 0 ;
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ; //多线程竞争锁进入时的单向链表
FreeNext = NULL ;
_EntryList = NULL ; //_owner从该双向循环链表中唤醒线程结点,_EntryList是第一个节点
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0; // 前一个拥有此监视器的线程ID
}
主要方法:
bool try_enter (TRAPS) ;
// 重量级锁入口方法
void enter(TRAPS);
// monitor 释放
void exit(bool not_suspended, TRAPS);
// 等待方法
void wait(jlong millis, bool interruptable, TRAPS);
// 唤醒方法
void notify(TRAPS);
void notifyAll(TRAPS);
synchronized锁队列协作流程比较复杂,所以源码的本博客就不详细描述,读者可以参考博客synchronized实现原理 小米信息部技术团队,里面有对源码做一个比较清晰的分析
在jdk6中,java虚拟机团队对锁进行了重要的改进,为了优化其性能主要引入了偏向锁、轻量级锁、自旋锁、自适应自旋、锁消除、锁粗化等实现。
为了保证多线程的有效并发,会要求每个线程持有锁的尽可能短,大部分情况,上面原则是正确的,但是在实际程序运行过程,可能会有一系列操作对一个对象反复加锁和解锁,或者加锁放在循环体中,这种情况会带来不必要的性能问题,所以jvm会对这种情况进行锁的粗化处理。锁粗化就是将锁的作用范围限制得尽可能小,只在共享数据的作用域中才进行同步加锁。
public void doSomething(int size){
for(int i=0;i<size;i++){
synchronized(lock){
// do something
}
}
// 锁粗化为,jvm可能会对程序优化,改变synchronized同步锁的作用范围
synchronized(lock){
for(int i=0;i<size;i++){
}
}
}
通过逃逸分析发现其实没有别的线程产生竞争的可能,别的线程没有临界量的引用,虚拟机会直接去掉这个锁
StringBuffer
是线程安全的,因为这个类使用了很多synchronized锁,append方法也是
@Override
public synchronized StringBuffer append(Object obj) {
toStringCache = null;
super.append(String.valueOf(obj));
return this;
}
例子,所以给一个例子,使用append这个方法,不过只是单独在main里同步调用,因为sBuf变量是本地变量,append方法是同步操作,不会存在竞争(不会逃逸),所以这个程序运行过程jvm可能会进行锁消除,忽略Stringbuffer里的synchronized锁
public static String getStr(String str1, String str2) {
StringBuffer sBuf = new StringBuffer();
sBuf.append(str1);
sBuf.append(str2);
return sBuf.toString();
}
jdk6引入了偏向锁来优化无线程争用时性能,偏向也即偏向获得它的线程,无锁化执行。
当一个线程获取到锁后,这把锁就是偏向锁。偏向锁是在对象头中记录一个线程ID,当这个线程再次去获取锁时,会校验是否这个线程,如果是直接获取锁就可以。
偏向锁可以提高带有同步但是无线程争用的程序性能,带有效益权衡性质的优化方法。也就是开启偏向锁并不一定都是有利的,如果程序总是存在多个线程竞争的情况,使用偏向锁反而影响性能,可以使用命令关闭偏向锁
-XX:-UseBiasedLocking
jdk1.6引进了轻量级锁,轻量级锁是相对于重量级锁使用monitor而言的,前面学习,我们知道monitor是基于操作系统底层的线程同步原语。引进轻量级锁并不是为了替换重量级锁,而是为了在没有多线程竞争的前提下,使用轻量级锁,减少重量级锁使用操作系统底层互斥量带来的性能损耗。
所以轻量级锁适应条件是同一时间线程争用不严重的情况。“对于绝大部分的锁,在整个同步周期内都是不存在竞争的”,这是一个经验,如果满足,使用轻量级锁当然可以带来性能提升,如果存在竞争,则可能比重量级锁更慢。思考:轻量级锁使用什么来做互斥量?
答案是cas锁,轻量级锁使用对象头中的mark work来做互斥判断
以上是java对象处于5种不同状态时,Mark Work
中64个位的表现形式,每一行表示对象处于某种状态时的样子。其中各部分参数的意义:
biased_lock
为1时表示启动偏向锁,为0时表示对象没有启用偏向锁,biased_lock
和lock
配合表示的意义biased_lock | lock | 状态 |
---|---|---|
0 | 01 | 无锁 |
1 | 01 | 偏向锁 |
-XX:MaxTenuringThreshold
进行设置System.identityHashCode()
计算,并会将结果写到该对象头中。当对象加锁后(偏向、轻量级、重量级),MarkWord的字节没有足够的空间保存hashCode,因此该值会移动到管程Monitor中。轻量级锁的使用过程:
mark word
的lock标识为00,成功就获得锁,失败就是有竞争,自旋,自旋获取不到,转为重量级锁自旋是一种获取锁的机制,并不是锁的状态。如果业务场景比较简单,可以比较快完成,同时又有多个处理器的情况,则抢不到锁的线程是可以通过循环自旋的方式去获取锁。jdk1.4.2引入,默认关闭,jdk1.6改为默认开启,开关参数:
-XX:+UseSpinning
优缺点:如果锁占用时间很短,自旋等待的效果是不错的,反之会耗费处理器资源。同时自旋对处理器数量也有要求,必须要有多个处理器。
自适应自旋:jdk1.6引入了自适应自旋,意味着自旋时间不再固定,而是由前一次在同个锁上的自旋时间及锁的持有者状态来决定的。如果在同一个锁对象上,自旋等待获得锁,并且持有锁的线程正在运行,那么虚拟机就会认为这次自旋是成功的,进而它允许自旋等待更长的时间。如果很少成功获取锁,那在以后去抢锁时可能省略自旋的过程。有了自适应自旋,虚拟机对锁的状况预测会越来越准确。
锁的升级过程:无锁->偏向锁->轻量级锁->重量级锁
我正在尝试在多线程环境中实现某种累积逻辑;我想知道没有 lock 和 synchronized 关键字是否有更好/更快的方法来做到这一点?以下是我当前的代码: public class Concurr
我需要帮助构建一个实现信号量的监视器,简单的 C 示例就可以。 这是为了证明可以在任何可以使用信号量的地方使用监视器。 最佳答案 如果您说允许使用互斥锁/condvars,请检查: #include
我已经构建了一些返回部分产品目录的 ajax,并且我正在尝试将 xml 输出到文档中,到目前为止,这是我所拥有的: $("#catalog").append("Item NamePriceDe
很抱歉,如果我的问题之前已经被问过,或者它太明显了,但我真的需要澄清这一点。感谢您的帮助。 在多用户界面中,如果来自不同用户的相同事务同时到达服务器,会发生什么? 我有下一张表: create tab
这可能是一个愚蠢的问题,但是这个程序的输出(它的方式)可以为零吗? public class Test2{ int a = 0; AtomicInteger b = new Atomi
假设我本地主机上的一个网站处理每个请求大约需要 3 秒。这很好,正如预期的那样(因为它在幕后进行了一些奇特的网络)。 但是,如果我在选项卡(在 firefox 中)中打开相同的 url,然后同时重新加
我对 MongoDB 的读锁定有点困惑。单个集合可以支持多少个并发读取操作? 最佳答案 如 tk 给出的链接中所写:http://www.mongodb.org/pages/viewpage.acti
如果有四个并发的 CUDA 应用程序在一个 GPU 中竞争资源会发生什么这样他们就可以将工作卸载到图形卡上了? Cuda Programming Guide 3.1 提到那里 某些方法是异步的: 内核
👊上次的百度面试遇到了关于spark的并发数的问题,今天我们就来将这些问题都一并解决一下,图画的的有点丑,还行大家见谅,百度实习的问题我放在了下面的链接👇: 链接: 2022百度大数据开发工程师实
我对 Groovy 线程有疑问。 我的任务是以某种方式翻译给定目录中的每个文件 并将生成的输出放在其他目录中的文件中。 我编写了以下代码,该代码有效: static def translateDir(
Java中的同步和锁定有什么区别? 最佳答案 synchronized是语言关键字;锁是对象。 当一个方法或代码块被标记为同步时,您是说该方法或代码块必须先获得某个锁对象(可以在同步的语法中指定)才能
我需要创建一个能够同时处理来自客户端的多个请求的并发 RPC 服务器。 使用 rpcgen linux编译器(基于sun RPC),不支持-A为并发服务器创建 stub 的选项。 (-A 选项在 so
System.out.println("Enter the number of what you would like to do"); System.out.println("1 = Manuall
我正在将我的应用程序移植到 iOS 8.0 并注意到 UIAlertView 已被弃用。 所以我改变了使用 UIAlertController 的方法。这在大多数情况下都有效。 除了,当我的应用程序打
我正在逐行同时读取两个文本文件。 我特别想做的是当lineCount在每个线程上都是相同的我想看看扫描仪当前正在读取的字符串。 我环顾四周寻找可以实现的某些模式,例如 Compare and Swap
我正在阅读 Java Concurrency in Practice .在章节中断政策部分 取消和关闭 它提到 A task should not assume anything about the
我正在尝试学习线程,互斥等的基础知识。遵循here的文档和示例。在下面的代码中,我得到预期的输出。问题: 想确认我是否有任何陷阱?我们如何改善下面的代码? 我的线程在哪一行尝试获取互斥锁或正在等待互斥
并发是指两个任务在不同的线程上并行运行。但是,异步方法并行运行,但在同一个线程上。这是如何实现的?另外,并行性怎么样? 这三个概念有什么区别? 最佳答案 并发和并行实际上与您正确推测的原理相同,两者都
以此ConcurrentDouble类定义为例: public class ConcurrentDouble { public double num = 0; public void subt
在得知并发确实增加了许多人的吞吐量后,我一直计划在项目中使用并发。现在我在多线程或并发方面还没有做太多工作,因此决定在实际项目中使用它之前学习并进行简单的概念验证。 以下是我尝试过的两个示例: 1.
我是一名优秀的程序员,十分优秀!