河北建设信息网站,自己可以做网站生意好做吗,做外贸好的网站,wordpress付费破解Synchronized使用方式
Synchronized有三种应用方式
作用于实例方法#xff0c;当前示实例加锁进入同步代码前要获得当前实例的锁#xff0c;即synchronized普通同步方法#xff0c;调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置。 如果设置了#xff0c;执行…Synchronized使用方式
Synchronized有三种应用方式
作用于实例方法当前示实例加锁进入同步代码前要获得当前实例的锁即synchronized普通同步方法调用指令将会检查方法的ACC_SYNCHRONIZED访问标志是否被设置。 如果设置了执行线程会将先持有monitor然后再执行方法 最后在方法完成(无论是正常完成还是非正常完成)时释放 monitor作用于代码块对括号里面配置的对象加锁即synchronized同步代码块实现使用的是monitorenter和monitorexit指令作用于静态方法当前类加锁进去同步代码前要获得当前对象的锁即synchronized静态同步方法ACC_STATIC, ACC_SYNCHRONIZED访问标志区分该方法是否静态同步方法
synchronized同步代码块里面一定是一个enter两个exit吗
不一定方法里面添加异常的话会是1-1 实现原理
Monitor
管程 (英语Monitors也称为监视器) 是一种程序结构结构内的多个子程序对象或模块形成的多个工作线程互斥访问共享资源。 这些共享资源一般是硬件设备或一群变量。对共享变量能够进行的所有操作集中在一个模块中。把信号量及其操作原语“封装”在一个对象内部管程实现了在一个时间点最多只有一个线程在执行管程的某个子程序。管程提供了一种机制管程可以看做一个软件模块它是将共享的变量和对于这些共享变量的操作封装起来形成一个具有一定接口的功能模块进程可以调用管程来实现进程级别的并发控制。
Monitor其实是一种同步机制他的义务是保证只有一个线程可以访问被保护的数据和代码。JVM中的同步是基于进入和退出监视器对象来实现的每一个对象实例都会有一个Monitor对象Monitor对象会和Java对象一同创建并销毁底层由C实现
Java虚拟机可以支持方法级的同步和方法内部一段指令序列的同步这两种同步结构都是使用管程(Monitor更常见的是直接将它称为“锁”来实现的。
方法级的同步是隐式的无须通过字节码指令来控制它实现在方法调用和返回操作之中。虚拟机可以从方法常量池中的方法表结构中的ACC_SYNCHRONIZED访问标志得知一个方法是否被声明为同步方法。当方法调用时调用指令将会检查方法ACC_SYNCHRONIZED访问标志是否被设置如果设置了执行线程就要求先成功持有管程然后才能执行方法最后当方法完成(无论是正常完成还是非正常完成时释放管程。在方法执行期间执行线程持有了管程其他任何线程都无法再获取到同一个管程。如果一个同步方法执行期间抛出了异常并且在方法内部无法处理此异常那这个同步方法所持有的管程将在异常抛到同步方法边界之外时自动释放。
同步一段指令集序列通常是由Java语言中的synchronized语句块来表示的Java虚拟机的指令集中有monitorenter和monitorexit两条指令来支持synchronized关键字的语义正确实现synchronized关键字需要Javac编译器与Java虚拟机两者共同协作支持
在HotSpot虚拟机中monitor采用ObjectMonitor实现。ObjectMonitor.java→ObjectMonitor.cpp→objectMonitor.hpp !
_owner指向持有ObjectMonitor对象的线程_WaitSet存放处于wait状态的线程队列即调用wait()方法的线程_EntryList存放处于等待锁block状态的线程队列_count约为_WaitSet 和 _EntryList 的节点数之和_cxq: 多个线程争抢锁会先存入这个单向链表_recursions: 记录重入次数
Synchronized关键字在编译成class文件后如果是方法块使用会在方法块开始和结束的JVM指令之间插入monitorenter和monitorexit指令如果作用在方法上会将方法标志位ACC_SYNCHORONIZED,JVM编译时会自动添加上述指令。
每个对象都存在着一个 Monitor对象与之关联。执行 monitorenter 指令就是线程试图去获取 Monitor 的所有权抢到了就是成功获取锁了执行 monitorexit 指令则是释放了Monitor的所有权。
Monitor与java对象以及线程是如何关联 1.如果一个java对象被某个线程锁住则该java对象头的Mark Word字段中LockWord指向monitor的起始地址 2.Monitor的Owner字段会存放拥有相关联对象锁的线程id
java的线程是映射到操作系统原生线程之上的如果要阻塞或唤醒一个线程就需要操作系统介入需要在户态与核心态之间切换这种切换会消耗大量的系统资源因为用户态与内核态都有各自专用的内存空间专用的寄存器等用户态切换至内核态需要传递给许多变量、参数给内核内核也需要保护好用户态在切换时的一些寄存器值、变量等以便内核态调用结束后切换回用户态继续工作。
在Java早期版本中synchronized属于重量级锁效率低下因为监视器锁monitor是依赖于底层的操作系统的Mutex Lock来实现的挂起线程和恢复线程都需要转入内核态去完成阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成这种状态切换需要耗费处理器时间如果同步代码块中内容过于简单这种切换的时间可能比用户代码执行的时间还长”时间成本相对较高这也是为什么早期的synchronized效率低的原因 Java 6之后为了减少获得锁和释放锁所带来的性能消耗引入了轻量级锁和偏向锁
synchronized用的锁是存在Java对象头里的Mark Word中 锁升级功能主要依赖MarkWord中锁标志位和释放偏向锁标志位
可重入原理
每个monitor对象拥有一个锁计数器和一个指向持有该锁的线程的指针。当执行monitorenter时如果目标monitor对象的计数器为零那么说明它没有被其他线程所持有Java虚拟机会将该锁对象的持有线程设置为当前线程并且将其计数器加1。在目标monitor对象的计数器不为零的情况下如果monitor对象的持有线程是当前线程那么 Java 虚拟机可以将其计数器加1否则需要等待直至持有线程释放该锁。当执行monitorexit时Java虚拟机则需将锁对象的计数器减1。计数器为零代表锁已被释放。
JVM底层
管程是一种程序结构程把信号量及其操作原语“封装”在一个对象内部实现了在一个时间点最多只有一个线程在执行管程的某个子程序。
Monitor依赖于底层的操作系统的Mutex Lock,也是一种同步机制保证只有一个线程可以访问被保护的数据和代码。
java中每一个对象实例都会有一个Monitor对象Monitor对象会和Java对象一同创建并销毁JVM中的synchronized是基于进入和退出监视器对象来实现的分别对应两个jvm指令monitorenter和monitorexit实现逻辑由底层由C的ObjectMonitor.cpp实现
monitorenter由ObjectMonitor::enter实现
首先尝试通过 CAS 把 ObjectMonitor 中的 _owner 设置为当前线程设置成功就表示获取锁成功。(通过 _recursions 的自增来表示重入)。如果没有CAS成功那么就开始启动自适应自旋自旋还不行的话就包装成 ObjectWaiter 对象加入到 _cxq存放竞争锁的的线程 单向链表之中。加入_cxq链表后再次尝试是否可以CAS拿到锁再次失败就要阻塞(block)底层调用了pthread_mutex_lock。monitorexit由ObjectMonitor::exit()实现 解锁过程会先判断_recursions字段是否是0如果不是说明是重入锁字段--即可如果等于根据不同的策略决定线程节点放在那里然后唤醒等待队列中的线程。线程解锁后还会唤醒之前等待的线程。当线程执行 Object.notify()方法时从 _waitSet 头部拿线程节点然后根据策略QMode指定决定将线程节点放在哪里包括_cxq 或 _EntryList 的头部或者尾部然后唤醒队列中的线程。可重入锁就是根据 _recursions 来判断的重入一次就执行 _recursions解锁一次就执行 _recursions--如果 _recursions 减到 0 就说明需要释放锁了。对于方法级的实现是隐式的当方法调用时调用指令前会检查方法ACC_SYNCHRONIZED访问标志是否被设置如果设置了执行线程就要求先成功持有管程即执行monitorenter然后才能执行方法最后当方法完成时释放管程。在方法执行期间执行线程持有了管程其他任何线程都无法再获取到同一个管程。
对于代码块方法实现的锁会在代码块中植入monitorenter和monitorrexit这两个JVM指令来实现的如果代码块中显示抛出异常那么就只会生成一个moniotrenter的一个monitorexit如果没有就会生成两个monitorexit一个用于正常执行释放一个用于异常执行释放。
当一个线程monitorenter执行成功就会将该对象的对象头的MarkWord字段中的LockWord指向相应monitor对象的起始地址并且该monitor对象的owner字段会存放该线程的id,count计数器1和对其他变量的操作执行monitorexit就是将变量复位或者其他变量操作。
对于synchronized锁升级: 无锁-》偏向锁-》轻量级锁-》重量级锁
synchorized.cpp
//偏向锁
static void fast_enter(Handle obj, BasicLock* lock, bool attempt_rebias,TRAPS);
static void fast_exit(oop obj, BasicLock* lock, Thread* THREAD);//轻量级
static void slow_enter(Handle obj, BasicLock* lock, TRAPS);
static void slow_exit(oop obj, BasicLock* lock, Thread* THREAD);//重量级锁
static void jni_enter(Handle obj, TRAPS);
static void jni_exit(oop obj, Thread* THREAD);synchronized锁升级过程总结一句话就是先自旋不行再阻塞。 实际上是把之前的悲观锁(重量级锁)变成在一定条件下使用偏向锁以及使用轻量级(自旋锁CAS)的形式
偏向锁:适用于单线程适用的情况在不存在锁竞争的时候进入同步方法/代码块则使用偏向锁。 轻量级锁适用于竞争较不激烈的情况(这和乐观锁的使用范围类似) 存在竞争时升级为轻量级锁轻量级锁采用的是自旋锁如果同步方法/代码块执行时间很短的话采用轻量级锁虽然会占用cpu资源但是相对比使用重量级锁还是更高效。 重量级锁适用于竞争激烈的情况如果同步方法/代码块执行时间很长那么使用轻量级锁自旋带来的性能消耗就比使用重量级锁更严重这时候就需要升级为重量级锁。