Synchronized简介
基本上所有的并发模式在解决线程冲突问题时,都是采用序列化访问共享资源的方案,即在给定时刻只允许一个任务访问共享资源,通常是在代码前面加上一条锁语句来实现,表示在某一时间段内只有一个任务可以运行这段代码,锁语句产生的互斥效果的机制被称为互斥量,
synchronized
用于修饰方法和代码块。
JDK5.0以前解决线程安全问题的方式:
//同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:
-
操作共享数据的代码即为需要被同步的代码
-
共享数据,多个线程共同操作的变量,比如
ticket
就是共享数据 -
同步监视器:俗称”锁“,任何一个类的对象都可以充当锁 要求:多个线程必须共用同一把锁
使用synchronized
关键字的注意事项:
- 一把锁只能同时被一个线程获取,没有获得锁的线程只能等待
- 每个实例都对应有自己的一把锁(`this`),不同实例之间互不影响
- 如果锁对象是`*.class`以及`synchronized`修饰的`static`方法时,所有对象共用一把锁
- `synchronized`修饰的方法,无论方法正常执行完毕还是抛出异常,都会释放锁
synchronized的使用方式
synchronized
分为对象锁、普通方法锁以及类锁;
对象锁
手动指定锁对象,也可以是
this
,也可以是自定义对象锁
public class SynchronizedObjectLock implements Runable{
static SynchronizedObjectLock instance = new SynchronizedObjectLock();
@Override
public void run(){
synchronized(this){
System.out.println("我是线程:"+Thread.currentThread().getName());
try{
Thread.sleep(3000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"结束");
}
}
public static void main(String[] args){
Thread t1=new Thread(instance);
Thread t2=new Thread(instance);
t1.start();
t2.start();
}
}
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
public class SynchronizedObjectLock implements Runable{
static SynchronizedObjectLock instance = new SynchronizedObjectLock();
Object block1 = new Object();
Object block2 = new Object();
@Override
public void run(){
synchronized(block1){
System.out.println("block1锁,我是线程"+Thread.currentThread().getName());
try{
Thread.sleep(3000);
}catch(InterruptedException){
e.printStackTrace();
}
System.out.println("block1锁,"+Thread.currentThread().getName()+"结束");
}
synchronized(block2){
System.out.println("block2锁,我是线程"+Thread.currentThread().getName());
try{
Thread.sleep(3000);
}catch(InterruptedException){
e.printStackTrace();
}
System.out.println("block2锁,"+Thread.currentThread().getName()+"结束");
}
}
public static void main(String[] args){
Thread t1=new Thread(instance);
Thread t2=new Thread(instance);
t1.start();
t2.start();
}
}
block1锁,我是线程Thread-0
block1锁,Thread-0结束
block2锁,我是线程Thread-0 // 可以看到当第一个线程在执行完第一段同步代码块之后,第二个同步代码块可以马上得到执行,因为他们使用的锁不是同一把
block1锁,我是线程Thread-1
block2锁,Thread-0结束
block1锁,Thread-1结束
block2锁,我是线程Thread-1
block2锁,Thread-1结束
普通方法锁
如果操作共享数据的代码完整的声明在一个方法中,我们就可以将此方法声明为一个同步方法,锁对象默认是
this
public class SynchronizedObjectLock implements Runnable{
static SynchronizedObjectLock instance = new SynchronizedObjectLock();
@Override
public void run(){
method();
}
public synchronized void method(){
System.out.println("我是线程"+Thread.currentThread().getName());
try{
Thread.sleep(3000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"结束");
}
public static void main(String[] args){
Thread t1=new Thread(instance);
Thread t2=new Thread(instance);
t1.start();
t2.start();
}
}
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
如果同一个类的多个方法上都添加了synchronized,那么这几个方法在同步执行时也是互斥的(抢占锁)
类锁
上述同步代码块和同步方法都是采用的对象锁,因此只能在并发调用同一个对象的方法时才会互斥,如果是多个对象分开调用,并不会产生互斥,类锁可以解决这一问题。
所谓类锁是指synchronized
修饰静态方法或指定锁对象为Class对象
public class SynchronizedObjectLock implements Runnable{
static SynchronizedObjectLock instance1 = new SynchronizedObjectLock();
static SynchronizedObjectLock instance2 = new SynchronizedObjectLock();
@Override
public void run(){
method();
}
//synchronized修饰static方法,默认锁为当前所在Class类,无论什么线程访问都只有这一把锁
public static synchronized void method(){
System.out.println("我是线程"+Thread.currentThread().getName());
try{
Thread.sleep(3000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"结束");
}
public static void main(String[] args){
Thread t1 = new Thread(instance1);
Thread t2 = new Thread(instance2);
t1.start();
t2.start();
}
}
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
public class SynchronizedObjectLock implements Runnable{
static SynchronizedObjectLock instance1 = new SynchronizedObjectLock();
static SynchronizedObjectLock instance2 = new SynchronizedObjectLock();
@Override
public void run(){
synchronized(SynchronizedObjectLock.class){
System.out.println("我是线程"+Thread.currentThread().getName());
try{
Thread.sleep(3000);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"结束");
}
}
public static void main(String[] args){
Thread t1 = new Thread(instance1);
Thread t2 = new Thread(instance2);
t1.start();
t2.start();
}
}
我是线程Thread-0
Thread-0结束
我是线程Thread-1
Thread-1结束
Synchronized原理分析
synchronized
的实现机制就是对对象的加锁,Java中每个对象都隐含关联一个监视器ObjectMonitor
,监视器通过c++
实现内置在JVM
中,监视器地址记录在对象的MarkWord
上,synchronized
通过ObjectMonitor
实现对象的锁操作
JVM在内存中将对象划分为三部分:对象头、实例数据和对齐填充
当线程访问同步块时,首先需要获得锁并把相关信息存储在对象头中,这也是为什么wait
、notify
、notifyAll
这些方法设计在Object类中的原因
Hostspot
有两种对象头:
-
数组类型:使用
arrayOopDesc
来描述对象头 -
其他:使用
instanceOopDesc
来描述对象头
对象头由两部分组成:
-
MarkWord
:存储自身的运行时数据,例如HashCode
、GC
年龄、锁相关信息等内容 -
Klass Pointer
:类型指针指向它的类元数据的指针
对象头分为MarkWord
和类型指针两部分,其中MarkWord
用于存储对象自身的运行数据,如哈希值、GC
分代年龄等,这部分占用32位或64位(按操作系统位数决定)。
以下是32位的空间布局,MarkWord
会根据对象状态复用存储空间,例如对象未锁定状态下,采用25bit(HashCode)+4bit(GC年龄)+1bit(固定0)+2bit(锁标志位)
,当标志位=10
表示对象处于重量级锁定时,剩余空间就用于存储ObjectMonitor
对象的地址
以下是64位的Mark Word
结构
-
无锁状态时,前25bit不使用,31bit用于存储
hashCode
值 -
偏向锁状态时,前54bit用于存储线程的id,后2bit用于存储元数据
-
轻量级锁状态时,前62bit都用于存储指向线程栈中
Lock Record
的指针 -
重量级锁状态时,前62位用于存储指向互斥量的指针
64位MarkWord数据结构
重量级锁的底层原理
在重量级锁中,没有竞争到锁的对象会park被挂起,退出同步块时unpark唤醒后续线程,唤醒操作涉及到操作系统调度,会有额外的开销
ObjectMonitor
中包含一个同步队列(由_cxq
和_EntryList
组成)和一个等待队列(_WaitSet
)
-
被
notify
或notifyAll
唤醒时,根据policy
策略选择加入的队列(policy
默认为0) -
退出同步块时,根据
QMode
策略来唤醒下一个线程(QMode
默认为0)
并发的两大核心问题:互斥和同步都是可以通过管程来解决的
什么是管程?
管程是一把解决并发问题的万能钥匙,synchronized
关键字以及wait
、notify
、notifyAll
三个方法都是管程的组成部分
synchronized
的monitor
锁机制和JDK
并发包中的AQS
很相似,只不过AQS
中是一个同步队列、多个等待队列
源码解析
队列的协作流程图
在HotSpot中monitor是由ObjectMonitor实现的,其源码是用C++来实现的,源文件是ObjectMonitor.hpp
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 从该双向循环链表中唤醒线程,
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0; // 前一个拥有此监视器的线程 ID
}
代码解析
-
_owner
:初始时为NULL,当有线程占有该monitor时owner标记该线程的ID。当线程释放monitor时owner恢复为NULL。owner是一个临界资源,JVM是通过CAS操作来保证其线程安全 -
_cxq
:竞争队列所有请求锁的线程首先会被放入这个队列(单向队列)。_cxq是一个临界资源,JVM通过CAS原子指令来修改_cxq队列
这里需要注意:每当有新的节点入队,它的next指针总是指向之前队列的头节点,而_cxq指针会指向该新入队的节点,所以是后来居上
-
_EntryList
:_cxq队列中有资格成为候选资源的线程会被移动到该队列中 -
_WaitSet
:等待队列因为调用wait方法而被阻塞的线程会被放入该队列中 -
_recursions:计数器,用于计算一个线程进入同一个monitor的次数,同一个线程多次进入同一个monitor称为重入,重入锁的基本概念参考:
[可重入原理:加锁计数器](https://flowus.cn/24b26c9d-028d-4790-b716-892218472289)
加锁和释放锁的原理
/**
* @author caihuaxin
* @version 1.0.0
* @doc Synchronized演示
* @date 2023-08-17 11:05:06
*/
public class SynchronizedDemo {
Object object = new Object();
public void method(){
synchronized (object) {
}
method2();
}
private static void method2(){
}
}
# 编译java为字节码文件
javac SynchronizedDemo.java
# 查看字节码文件反编译后的流程
javap -verbose SynchronizedDemo.class
F:\Project\Study\Java8NewFeature\src\com\carl\test\synchronizedTest>javap -verbose SynchronizedDemo.class
Classfile /F:/Project/Study/Java8NewFeature/src/com/carl/test/synchronizedTest/SynchronizedDemo.class
Last modified 2023年10月23日; size 523 bytes
MD5 checksum 7492a9fa5c8fe48f7311971e46f286a0
Compiled from "SynchronizedDemo.java"
public class com.carl.test.synchronizedTest.SynchronizedDemo
minor version: 0
major version: 55
flags: (0x0021) ACC_PUBLIC, ACC_SUPER
this_class: #5 // com/carl/test/synchronizedTest/SynchronizedDemo
super_class: #2 // java/lang/Object
interfaces: 0, fields: 1, methods: 3, attributes: 1
Constant pool:
#1 = Methodref #2.#18 // java/lang/Object."<init>":()V
#2 = Class #19 // java/lang/Object
#3 = Fieldref #5.#20 // com/carl/test/synchronizedTest/SynchronizedDemo.object:Ljava/lang/Object;
#4 = Methodref #5.#21 // com/carl/test/synchronizedTest/SynchronizedDemo.method2:()V
#5 = Class #22 // com/carl/test/synchronizedTest/SynchronizedDemo
#6 = Utf8 object
#7 = Utf8 Ljava/lang/Object;
#8 = Utf8 <init>
#9 = Utf8 ()V
#10 = Utf8 Code
#11 = Utf8 LineNumberTable
#12 = Utf8 method
#13 = Utf8 StackMapTable
#14 = Class #23 // java/lang/Throwable
#15 = Utf8 method2
#16 = Utf8 SourceFile
#17 = Utf8 SynchronizedDemo.java
#18 = NameAndType #8:#9 // "<init>":()V
#19 = Utf8 java/lang/Object
#20 = NameAndType #6:#7 // object:Ljava/lang/Object;
#21 = NameAndType #15:#9 // method2:()V
#22 = Utf8 com/carl/test/synchronizedTest/SynchronizedDemo
#23 = Utf8 java/lang/Throwable
{
java.lang.Object object;
descriptor: Ljava/lang/Object;
flags: (0x0000)
public com.carl.test.synchronizedTest.SynchronizedDemo();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=3, locals=1, args_size=1
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: aload_0
5: new #2 // class java/lang/Object
8: dup
9: invokespecial #1 // Method java/lang/Object."<init>":()V
12: putfield #3 // Field object:Ljava/lang/Object;
15: return
LineNumberTable:
line 9: 0
line 10: 4
public void method();
descriptor: ()V
flags: (0x0001) ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: getfield #3 // Field object:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: aload_1
8: monitorexit
9: goto 17
12: astore_2
13: aload_1
14: monitorexit
15: aload_2
16: athrow
17: invokestatic #4 // Method method2:()V
20: return
Exception table:
from to target type
7 9 12 any
12 15 12 any
LineNumberTable:
line 12: 0
line 14: 7
line 15: 17
line 16: 20
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 12
locals = [ class com/carl/test/synchronizedTest/SynchronizedDemo, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
}
SourceFile: "SynchronizedDemo.java"
monitor
:监视器
monitorenter
:尝试获取monitor
的所有权,如果monitor
计数器为0,则获取到monitor
的所有权,并把锁计数器+1;如果计数器值大于0,其他线程再想获取就需要等待;如果这个monitor
拿到了锁的所有权,又重入了这把锁,那这个锁计数器就会累加,计数器的值=
同一个monitor
进入锁的次数
monitorexit
:释放monitor
的所有权,monitor
的锁计数器-1,如果-1后,计数器值不为0,则代表刚才是重入进来的,当前现成还继续持有这把锁的所有权,直到monitor
的计数器为0,才真正释放锁
源码分析上述代码的执行流程
monitor竞争
-
通过CAS尝试把monitor的owner字段设置为当前线程ID
-
如果设置之前的owner指向当前线程,则说明当前线程是重入monitor,重入锁则会执行recursions++,记录重入的次数
-
如果当前线程是第一次进入该monitor,则设置rescursions为1,_owner为当前线程,该线程成功获取锁并返回
-
如果获取锁失败,则等待锁的释放
IRT_ENTRY_NO_ASYNC(void, InterpreterRuntime::monitorenter(JavaThread* thread, BasicObjectLock* elem))
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
if (PrintBiasedLockingStatistics) {
Atomic::inc(BiasedLocking::slow_path_entry_count_addr());
}
Handle h_obj(thread, elem->obj());
assert(Universe::heap()->is_in_reserved_or_null(h_obj()),"must be NULL or an object");
// 是否使用偏向锁 JVM 启动时设置的偏向锁-XX:-UseBiasedLocking=false/true
if (UseBiasedLocking) {
// Retry fast entry if bias is revoked to avoid unnecessary inflation
ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
} else {
// 轻量级锁
ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK);
}
assert(Universe::heap()->is_in_reserved_or_null(elem->obj()),
"must be NULL or an object");
#ifdef ASSERT
thread->last_frame().interpreter_frame_verify_monitor(elem);
#endif
IRT_END
slow_enter方法主要是轻量级锁的操作,如果操作失败会膨胀为重量级锁,enter方法则为重量级锁的入口,源码如下:
void ATTR ObjectMonitor::enter(TRAPS) {
Thread * const Self = THREAD ;
void * cur ;
// 省略部分代码
// 通过 CAS 操作尝试把 monitor 的_owner 字段设置为当前线程
cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
if (cur == NULL) {
assert (_recursions == 0 , "invariant") ;
assert (_owner == Self, "invariant") ;
return ;
}
// 如果当前线程重入该monitor,则recursions++
if (cur == Self) {
_recursions ++ ;
return ;
}
// 如果当前线程是第一次进入该 monitor, 设置_recursions 为 1,_owner 为当前线程
if (Self->is_lock_owned ((address)cur)) {
assert (_recursions == 0, "internal state error");
_recursions = 1 ;
_owner = Self ;
OwnerIsThread = 1 ;
return ;
}
for (;;) {
jt->set_suspend_equivalent();
// 如果获取锁失败,则等待锁的释放;
EnterI (THREAD) ;
if (!ExitSuspendEquivalent(jt)) break ;
_recursions = 0 ;
_succ = NULL ;
exit (false, Self) ;
jt->java_suspend_self();
}
Self->set_current_pending_monitor(NULL);
}
}
monitor等待
-
当线程进入等待状态,即被封装为ObjectWaiter对象node,状态设置为ObjectWaiter::TS_CXQ
-
for循环通过CAS把node节点push到_cxq列表中,同一时刻可能有多个线程把自己的node节点push到_cxq列表中
-
node节点push到_cxq列表之后,通过自旋锁尝试获取锁,如果还是没有获取到锁,则通过park将当前线程挂起等待被唤起
-
当该线程被唤醒时,会从挂起点继续执行,通过ObjectMonitor::TryLock尝试获取锁
// 省略部分代码
void ATTR ObjectMonitor::EnterI (TRAPS) {
Thread * Self = THREAD ;
assert (Self->is_Java_thread(), "invariant") ;
assert (((JavaThread *) Self)->thread_state() == _thread_blocked , "invariant") ;
// Try lock 尝试获取锁
if (TryLock (Self) > 0) {
assert (_succ != Self , "invariant") ;
assert (_owner == Self , "invariant") ;
assert (_Responsible != Self , "invariant") ;
// 如果获取成功则退出,避免 park unpark 系统调度的开销
return ;
}
// 自旋获取锁
if (TrySpin(Self) > 0) {
assert (_owner == Self, "invariant");
assert (_succ != Self, "invariant");
assert (_Responsible != Self, "invariant");
return;
}
// 当前线程被封装成 ObjectWaiter 对象 node, 状态设置成 ObjectWaiter::TS_CXQ
ObjectWaiter node(Self) ;
Self->_ParkEvent->reset() ;
node._prev = (ObjectWaiter *) 0xBAD ;
node.TState = ObjectWaiter::TS_CXQ ;
// 通过 CAS 把 node 节点 push 到_cxq 列表中
ObjectWaiter * nxt ;
for (;;) {
node._next = nxt = _cxq ;
if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;
// 再次 tryLock
if (TryLock (Self) > 0) {
assert (_succ != Self , "invariant") ;
assert (_owner == Self , "invariant") ;
assert (_Responsible != Self , "invariant") ;
return ;
}
}
for (;;) {
// 本段代码的主要思想和 AQS 中相似可以类比来看
// 再次尝试
if (TryLock (Self) > 0) break ;
assert (_owner != Self, "invariant") ;
if ((SyncFlags & 2) && _Responsible == NULL) {
Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
}
// 满足条件则 park self
if (_Responsible == Self || (SyncFlags & 1)) {
TEVENT (Inflated enter - park TIMED) ;
Self->_ParkEvent->park ((jlong) RecheckInterval) ;
// Increase the RecheckInterval, but clamp the value.
RecheckInterval *= 8 ;
if (RecheckInterval > 1000) RecheckInterval = 1000 ;
} else {
TEVENT (Inflated enter - park UNTIMED) ;
// 通过 park 将当前线程挂起,等待被唤醒
Self->_ParkEvent->park() ;
}
if (TryLock(Self) > 0) break ;
// 再次尝试自旋
if ((Knob_SpinAfterFutile & 1) && TrySpin(Self) > 0) break;
}
return ;
}
monitor释放
当某个持有锁的线程执行完同步代码块时,会释放锁并unpark后续线程
void ATTR ObjectMonitor::exit(bool not_suspended, TRAPS) {
Thread * Self = THREAD ;
if (_recursions != 0) {
_recursions--; // this is simple recursive enter
TEVENT (Inflated exit - recursive) ;
return ;
}
ObjectWaiter * w = NULL ;
int QMode = Knob_QMode ;
// 直接绕过 EntryList 队列,从 cxq 队列中获取线程用于竞争锁
if (QMode == 2 && _cxq != NULL) {
w = _cxq ;
assert (w != NULL, "invariant") ;
assert (w->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
ExitEpilog (Self, w) ;
return ;
}
// cxq 队列插入 EntryList 尾部
if (QMode == 3 && _cxq != NULL) {
w = _cxq ;
for (;;) {
assert (w != NULL, "Invariant") ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
if (u == w) break ;
w = u ;
}
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
for (p = w ; p != NULL ; p = p->_next) {
guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
ObjectWaiter * Tail ;
for (Tail = _EntryList ; Tail != NULL && Tail->_next != NULL ; Tail = Tail->_next) ;
if (Tail == NULL) {
_EntryList = w ;
} else {
Tail->_next = w ;
w->_prev = Tail ;
}
}
// cxq 队列插入到_EntryList 头部
if (QMode == 4 && _cxq != NULL) {
// 把 cxq 队列放入 EntryList
// 此策略确保最近运行的线程位于 EntryList 的头部
w = _cxq ;
for (;;) {
assert (w != NULL, "Invariant") ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
if (u == w) break ;
w = u ;
}
assert (w != NULL , "invariant") ;
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
for (p = w ; p != NULL ; p = p->_next) {
guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
if (_EntryList != NULL) {
q->_next = _EntryList ;
_EntryList->_prev = q ;
}
_EntryList = w ;
}
w = _EntryList ;
if (w != NULL) {
assert (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
ExitEpilog (Self, w) ;
return ;
}
w = _cxq ;
if (w == NULL) continue ;
for (;;) {
assert (w != NULL, "Invariant") ;
ObjectWaiter * u = (ObjectWaiter *) Atomic::cmpxchg_ptr (NULL, &_cxq, w) ;
if (u == w) break ;
w = u ;
}
if (QMode == 1) {
// QMode == 1 : 把 cxq 倾倒入 EntryList 逆序
ObjectWaiter * s = NULL ;
ObjectWaiter * t = w ;
ObjectWaiter * u = NULL ;
while (t != NULL) {
guarantee (t->TState == ObjectWaiter::TS_CXQ, "invariant") ;
t->TState = ObjectWaiter::TS_ENTER ;
u = t->_next ;
t->_prev = u ;
t->_next = s ;
s = t;
t = u ;
}
_EntryList = s ;
assert (s != NULL, "invariant") ;
} else {
// QMode == 0 or QMode == 2
_EntryList = w ;
ObjectWaiter * q = NULL ;
ObjectWaiter * p ;
// 将单向链表构造成双向环形链表;
for (p = w ; p != NULL ; p = p->_next) {
guarantee (p->TState == ObjectWaiter::TS_CXQ, "Invariant") ;
p->TState = ObjectWaiter::TS_ENTER ;
p->_prev = q ;
q = p ;
}
}
if (_succ != NULL) continue;
w = _EntryList ;
if (w != NULL) {
guarantee (w->TState == ObjectWaiter::TS_ENTER, "invariant") ;
ExitEpilog (Self, w) ;
return ;
}
}
}
notify线程唤醒
notify或者notifyAll方法可以唤醒同一个锁监视器下调用wait挂起的线程
void ObjectMonitor::notify(TRAPS) {
CHECK_OWNER();
if (_WaitSet == NULL) {
TEVENT (Empty - Notify);
return;
}
DTRACE_MONITOR_PROBE(notify, this, object(), THREAD);
int Policy = Knob_MoveNotifyee;
Thread::SpinAcquire(&_WaitSetLock, "WaitSet - notify");
ObjectWaiter *iterator = DequeueWaiter();
if (iterator != NULL) {
// 省略一些代码
// 头插 EntryList
if (Policy == 0) {
if (List == NULL) {
iterator->_next = iterator->_prev = NULL;
_EntryList = iterator;
} else {
List->_prev = iterator;
iterator->_next = List;
iterator->_prev = NULL;
_EntryList = iterator;
}
} else if (Policy == 1) { // 尾插 EntryList
if (List == NULL) {
iterator->_next = iterator->_prev = NULL;
_EntryList = iterator;
} else {
ObjectWaiter *Tail;
for (Tail = List; Tail->_next != NULL; Tail = Tail->_next);
assert (Tail != NULL && Tail->_next == NULL, "invariant");
Tail->_next = iterator;
iterator->_prev = Tail;
iterator->_next = NULL;
}
} else if (Policy == 2) { // 头插 cxq
// prepend to cxq
if (List == NULL) {
iterator->_next = iterator->_prev = NULL;
_EntryList = iterator;
} else {
iterator->TState = ObjectWaiter::TS_CXQ;
for (;;) {
ObjectWaiter *Front = _cxq;
iterator->_next = Front;
if (Atomic::cmpxchg_ptr(iterator, &_cxq, Front) == Front) {
break;
}
}
}
} else if (Policy == 3) { // 尾插 cxq
iterator->TState = ObjectWaiter::TS_CXQ;
for (;;) {
ObjectWaiter *Tail;
Tail = _cxq;
if (Tail == NULL) {
iterator->_next = NULL;
if (Atomic::cmpxchg_ptr(iterator, &_cxq, NULL) == NULL) {
break;
}
} else {
while (Tail->_next != NULL) Tail = Tail->_next;
Tail->_next = iterator;
iterator->_prev = Tail;
iterator->_next = NULL;
break;
}
}
} else {
ParkEvent *ev = iterator->_event;
iterator->TState = ObjectWaiter::TS_RUN;
OrderAccess::fence();
ev->unpark();
}
if (Policy < 4) {
iterator->wait_reenter_begin(this);
}
}
// 自旋释放
Thread::SpinRelease(&_WaitSetLock);
if (iterator != NULL && ObjectMonitor::_sync_Notifications != NULL) {
ObjectMonitor::_sync_Notifications->inc();
}
}
synchronized原理图
保证可见性的原理
Synchronized
的,即监视器锁规则:对同一个监视器的解锁,happens-before
应对该监视器的加锁
public class MonitorDemo {
private int a = 0;
public synchronized void writer() { // 1
a++; // 2
} // 3
public synchronized void reader() { // 4
int i = a; // 5
} // 6
}
在图中每一个箭头连接的两个节点就代表之间的happens-before
关系。
-
黑色的是通过程序顺序规则推导出来
-
红色的为监视器锁规则推导而出:线程A释放锁
happens-before
线程B加锁 -
蓝色的则是通过程序顺序规则和监视器锁规则推测出来
happens-before
关系,通过传递性规则进一步推导的happens-before
关系
现在我们来重点关注2→happens-before→5,通过这个关系我们可以得出什么?
根据happens-before
的定义中的一条:如果A→happens-before→B,则A的执行结果对B可见,并且A的执行顺序先于B。
线程A先对共享变量A进行加一,由2→happens-before→5关系可知线程A的执行结果对线程B可见,即线程B所读取到的a的值为1
锁优化
锁的优缺点对比
通过对各种类型的锁以及对锁优化的了解,对锁的优缺点有如下对比:
锁类型 | 优点 | 缺点 | 使用场景 |
---|---|---|---|
偏向锁 | 加锁和解锁不需要CAS操作,没有额外的性能消耗,和执行非同步方法相比仅存在纳秒级的差距 | 如果线程之间存在锁竞争,会带来额外的锁撤销的消耗 | 有同步但无竞争的程序 |
轻量级锁 | 竞争的线程不会阻塞,提高了响应速度 | 如果线程始终得不到锁竞争的线程,使用自旋会消耗CPU的性能 | 多线程交替执行同步块的情况下 |
重量级锁 | 线程竞争不适用自旋,会消耗CPU | 线程阻塞,响应时间缓慢,在多线程下,频繁的获取锁和释放锁,会带来巨大的性能消耗 | 锁竞争激烈的情况下 |
Synchronized与Lock
类别 | synchronized | Lock |
---|---|---|
存在层次 | Java关键字,存在于JVM层面 | 是一个类 |
锁的释放 | 1. 以获取锁的线程执行完同步代码,释放锁 |
- 线程执行发生异常,JVM会让线程释放锁|由开发者控制锁的释放,一般在finally块中释放锁,不然会导致线程死锁|
|锁的获取|假设线程A获得锁,线程B等待,如果线程A阻塞,B线程会一直等待|分情况而定,Lock有多个获取锁的方式,当前一个线程阻塞时,后一个线程可以不用一直等待|
|锁状态|无法判断|可以判断|
|锁类型|可重入、不可中断、非公平|可重入、可中断、可公平/非公平|
|性能|少量同步|大量同步|
Condition和Lock的结合:ReentrantLock
多线程竞争一个锁时,其余未得到锁的线程只能不停的尝试获取锁,而不能中断,高并发情况下会导致性能下降,ReentrantLock的lockInterruptibly()方法可以优先考虑响应中断
响应中断机制:当一个线程等待时间过长,它可以中断自己,然后ReentrantLock响应这个中断,不再让这个线程继续等待,避免了死锁
总结
synchronized是通过软件(JVM)实现的,简单易用,即使在JDK5之后有了Lock,仍然被广泛的使用。
-
使用Synchronized有哪些要注意的?
-
锁对象不能为空,因为锁的信息都保存在对象头里
-
作用域不宜过大,影响程序执行的速度,控制范围过大,编写代码也容易出错
-
避免死锁
-
在能选择的情况下,既不要用Lock也不要用synchronized关键字,用java.util.concurrent包中的各种各样的类,如果不用该包下的类,在满足业务的情况下,可以使用synchronized关键,因为代码量少,避免出错
-
-
synchronized是公平锁吗?
synchronized实际上是非公平的,新来的线程有可能立即获得监视器,而在等待区中等候已久的线程可能再次等待,这样有利于提高性能,但是也可能会导致饥饿现象。