synchronized学习:
什么是synchronized?
- Synchronized是Java中的关键字,由JVM实现的。
- Synchronized是Java中解决并发问题的一种最常用的方法,也是最简单的一种方法。
- 原子性:可以确保线程互斥的访问同步代码;
- 可见性:保证共享变量的修改能及时可见,通过Java内存模型中的,对一个变量unlock操作前,必须同步到主内存中;如果对一个变量进行lock操作,则会清空工作内存中此变量的值,在执行引擎使用此变量前,需要重新从主内存中load操作或assign操作初始化变量值。来保证可见性。
- 有序性:有效解决重排序问题,即:一个unlock操作先行发生(happen-before)于后面对同一个锁的lock操作。
使用场景:
synchronized 原理:
1、synchronized修饰的代码块中。
synchronized(this){
sout("xXXX");
}
- 查看synchronized修饰过的代码块编译后的字节码,会发现被synchronized修饰过的代码块前后会生成两个字节码指令:monitorenter、monitorexit;
- 这两个字节码指令的含义:当JVM执行到monitorenter和monitorexit两个指令实现同步。
- monitorenter : 每个对象都与一个监视器锁(monitor)相关联,monitor才会被锁定。线程执行monitorenter指令时会尝试获取monitor的所有权。
- 每个对象维护者一个记录着被锁定次数的计数器,对象未被锁定时,计数器为0。线程进入monitor(执行monitorenter指令)时,计数器设置为1;同一个线程再次获取到该对象锁时,计时器会再加1;当其他线程尝试获取monitor时,会阻塞,直至计数器为0。
- monitorexit:执行monitorexit的线程必须是Object所对应的monitor所有者。指令执行时,monitor进入数减1,如果减1后进入数为0,则线程退出monitor,不再占有monitor。此时其他线程可以尝试获取monitor所有权。
总:synchronized
底层通过一个monitor的对象来完成,另外wait和notify等方法也是依赖于monitor对象的,也就是说为什么只有同步方法块和中才能调用wait和notify。
- monitorenter : 每个对象都与一个监视器锁(monitor)相关联,monitor才会被锁定。线程执行monitorenter指令时会尝试获取monitor的所有权。
2、在同步方法中。
synchronized void mothod(){
sout("XXXXXXX");
}
反编译后结果中没有monitorenter和monitorexit指令,常量池中多了个ACC_SYNCHRONIZED标识符。
当方法调用时,调用指令会检查方法的ACC_SYNCHRONIZED访问标志是否被设置,如果设置了,执行线程会先获取monitor,获取成功后方能执行方法体,方法执行完再释放monitor。方法执行期间其他任何线程都无法再获取monitor对象。
monitor监视器
- monitor 可以理解为一种同步工具,或者同步机制,通常被描述成一个对象。
- 操作系统中管程的概念,具体实现原理ObjectMonitor
- 操作系统的管程
- 管程(monitor,监视器),是一种程序结构,结果内部的多个子程序(对象模块)形成多个工作线程互斥访问共享资源。其实现在一个时间点,最多只有一个线程在执行管程的某个子程序。
- ObjectMonitor 数据结构:在JVM中monitor(管程)是由ObjectMonitor实现的。
ObjectMonitor(){
_header=NULL;
_count=0;//记录owner线程获取锁的次数
_waiters=0;
_recursions=0;//锁重入的次数
_object=NULL;
_owner=NULL;//指向持有ObjectMonitor对象的线程。
_WaitSet=NULL;//处于wait状态的线程,会被加入到_WaitSet
_WaitSetLock=0;
_Responsible=NULL;
_succ =NULL;
_cxq=NULL;
FreeNext=NULL;
_EntryList=NULL;//处于等待锁block状态的线程,会被加入到该列表
_SpinFrq=0;
_SpinClock=0;
OwnerlsThread=0;
}
Monitor 工作机理:
- 想要获取到monitor的线程,首先进入到_EntryList队列。
- 当某线程获取到对象的monitor后,进入到Owner区域,设置为当前线程,计数器count加1;
- 如果线程调用wait()方法,则进入waitset队列,同时会释放monitor锁,将owner赋值为null,count自减1,进入waitset队列阻塞等待。
- 如有其他线程调用notify()/notifyAll(),会唤醒waitset中的某个线程,该线程再次尝试获取monitor锁,成功则进入owner区域。
- 同步方法执行完毕,线程退出临界区,会将owner置为null,并释放监视锁。
synchronized(object){//进入_EntryList队列
method();
object.wait();//进入_WaitSet队列
}
可重入锁,synchronized可重入?
- 可重入:指的是当前线程获取到当前锁后,如果后续操作仍然需要获取该对象锁时,可以不用再次重新获取,可以直接操作该对象(共享资源)。
- 可重入性是为了解决自己锁死自己的情况。当一个类的同步方法调用另外一个同步方法时,如果synchronized不支持可重入,进入方法1时当前线程已经获取到了锁,而在方法1中执行方法2时又尝试去获取锁,此时如果不支持重入,就需要等待方法1释放锁,把自己阻塞,导致自己锁死自己。
- synchronized的可重入是在执行monitorenter指令时,如果对象没有锁定,或当前线程已经拥有了这个对象的锁,则把计数器加1。通过此方法实现可重入性。
synchronized锁的竞争与升级?(Java对原生锁的优化)
- 1.6之前,monitor的实现完全依赖于底层操作系统的互斥锁来实现,也即是monitorenter和monitorexit;Java的线程与操作系统的原生线程有映射关系,如果要将一个线程进行阻塞或唤起均需要操作系统协助,需要从用户态切换到内核态来执行,这种切换十分昂贵。
- 优化:
- 自旋锁:即是线程进行阻塞操作之前先让线程自旋一段时间,可能在等待期间,别的线程已经解锁,这样就不需要再让线程执行阻塞操作,避免用户态切换到内核态。
- 对monitor的三种不同实现:偏向锁、轻量级锁、重量级锁。这三种锁是JDK优化synchronized的运行,当jvm检测到不同的金正状态时会自动切换到合适的锁实现,实现锁的升级和降级。
- 锁升级、降级的过程:
- 在没有竞争出现时,默认使用偏向锁,JVM会利用
CAS
操作,在对象头上的MarkWord部分设置线程ID,来表示偏向于当前线程,并不涉及真正的互斥锁。在很多场景下,大部分对象生命周期中最多会被一个线程锁定,使用偏向锁来降低无竞争开销。 - 当有另外一个线程试图锁定某一个被偏向锁锁过的对象,JVM会自动撤销偏向锁,切换为轻量级锁;轻量级锁依赖CAS操作MarkWord来试图获取锁,如果重试成功,就是用普通的轻量级锁,否则继续升级为重量级锁。
- 在没有竞争出现时,默认使用偏向锁,JVM会利用
- 锁升级、降级的过程:
- Java并发编程这个领域中synchronized关键字一直都是元老级的角色,很久之前很多人都会称它为 “重量级锁” 。但是,随着 Java SE 1.6 对 synchronized 进行了各种优化之后,有些情况下它就并不那么重,Java SE 1.6 中为了减少获得锁和释放锁带来的性能消耗而引入的
偏向锁和轻量级锁
。针对 synchronized 获取锁的方式,JVM 使用了锁升级的优化方式,就是先使用偏向锁优先同一线程再次获取锁,如果失败,就升级为 CAS 轻量级锁,如果失败就会短暂自旋,防止线程被系统挂起。最后如果以上都失败就升级为重量级锁。
synchronized是公平锁还是非公平锁?
- 非公平锁
synchronized和volatile关键字的区别?
synchronized关键字和volatile关键字不是对立的是互补的。
- volatile关键字是线程同步的轻量级实现,所以volatile的性能肯定比synchronized要好。但是volatile只能用于修饰变量;synchronized关键字可以修饰方法和代码块。
- volatile关键字能保证数据的可见性,但不保证原子性。synchronized两者都能保证。
- volatile关键字主要用于解决变量在多个线程之间的可见性,而synchronized关键字解决的是多线程之间访问资源的同步性。
synchronized与lock的区别?
- synchronized是关键字是有jvm底层实现,什么都帮我们做了;lock是一个接口,JDK层面有很丰富的API。
- synchronized会自动释放锁;Lock则需要我们手动释放锁。
- synchronized是不可中断的;Lock则是可以中断的。
- Lock可以知道是否获取到锁,synchronized则不能。
- synchronized可以锁定方法和代码块;Lock只能锁定代码块。
- Lock可以使用读锁提高多线程的读效率。
- synchronized是非公平的;ReentrantLock可以控制是否公平锁。
未完待续~