同步锁synchronized追溯本源

同步锁synchronized追溯本源

概念

synchronized:是Java中的关键字,是一种同步锁。

Java中锁分为以下几种:
乐观锁、悲观锁(syn)
独享锁(syn)、共享锁
公平锁、非公平锁(syn)
互斥锁(syn)、读写锁
可重入锁(syn)
分段锁
synchronized JDK1.6锁升级(无锁 -> 偏向锁 (非锁)-> 轻量级锁 -> 重量级锁(1.6前都是)

众所周知 synchronized 关键字是解决并发问题常用解决方案,有以下三种使用方式:

  • 同步普通方法,锁的是当前对象。

  • 同步静态方法,锁的是当前 Class 对象。

  • 同步块,锁的是 () 中的对象。

多线程特性
原子性:指一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执

可见性:是指多个线程访问一个资源时,该资源的状态、值信息等对于其他线程都是可见的。
有序性:指程序中代码的执行顺序 (编译器会重排)

package com.xiaohu.mybatis.main;

import java.util.concurrent.TimeUnit;

/**
 * @author 小胡哥哥
 * NO BB show your code
 */
public class SynchronizedTest {
    public static void main(String[] args) {
        SynchronizedTest test = new SynchronizedTest();
        //synchronized修饰实例方法
        new Thread(() -> test.testSync()).start();
        new Thread(() -> test.testSync()).start();

        //synchronized修饰静态方法
        /*new Thread(() -> SynchronizedTest.testSyncForStatic()).start();
        new Thread(() -> SynchronizedTest.testSyncForStatic()).start();*/

        //正常方法
        //new Thread(() -> test.test()).start();
        //new Thread(() -> test.test()).start();
    }

    //加锁方法
    public synchronized void testSync() {
        System.out.println("进入testSync方法>>>>>>>>>>>>>>>>>>>>>");
        try {
            //模拟方法体尚未执行完毕
            TimeUnit.HOURS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //加锁方法
    public synchronized static void testSyncForStatic() {
        System.out.println("进入testSYNC方法>>>>>>>>>>>>>>>>>>>>>");
        try {
//模拟方法体尚未执行完毕
            TimeUnit.HOURS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //正常方法
    public void test() {
        System.out.println("进入test方法>>>>>>>>>>>>>>>>>>>>>");
        try {
            //模拟方法体尚未执行完毕
            TimeUnit.HOURS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

通过测试,加锁的实例方法和静态方法只有一个线程进入了方法,其他的线程一直在等待,而没有加锁的方法多线程正常执行,同一时刻,只有一个线程保证了原子性
在这里插入图片描述

2.2 反汇编寻找锁实现原理

通过jdk自带的一个工具:javap,对字节码进行反编译:

public class Test {
private static Object object = new Object();
public synchronized void testMethod() {
System.out.println("Hello World -synchronized method ");
}
public static void main(String[] args) {
synchronized (object) {
System.out.println("Hello World -synchronized block ");
}
}
}

在这里插入图片描述
我们可以看到,经过被synchronized修饰的代码块,多了两个指令,一个monitorenter和一个monitorexit,也就是说jvm通过这两指令实现同步
在这里插入图片描述
我们可以看到,经过被synchronized修饰的方法,是通过ACC_SYNCHRONIZED 修饰,会隐式调用monitorenter和monitorexit这两个指令。

口说无凭,让我们继续深入源码探究:

通过查询jvm规范,我们了解到monitorenter的原理解释

https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-6.html#jvms-6.5.monitorenter
在这里插入图片描述
经过翻译得知:

monitorenter

每一个对象都会和一个监视器monitor关联。

监视器被占用时会被锁住,其他线程无法来获取该monitor。

当JVM执行某个线程的某个方法内部的monitorenter时,它会尝试去获取当前对象对应的monitor的所有权。其过程如下:

  1. 若monior的进入数为0,线程可以进入monitor,并将monitor的进入数置为1。当前线程成为

monitor的owner(所有者)

  1. 若线程已拥有monitor的所有权,允许它重入monitor,则进入monitor的进入数加1

  2. 若其他线程已经占有monitor的所有权,那么当前尝试获取monitor的所有权的线程会被阻塞,直

到monitor的进入数变为0,才能重新尝试获取monitor的所有权。

monitorexit

  1. 能执行monitorexit指令的线程一定是拥有当前对象的monitor的所有权的线程。

  2. 执行monitorexit时会将monitor的进入数减1。当monitor的进入数减为0时,当前线程退出

monitor,不再拥有monitor的所有权,此时其他被这个monitor阻塞的线程可以尝试去获取这个

monitor的所有权,monitorexit释放锁。

monitorexit插入在方法结束处和异常处,JVM保证每个monitorenter必须有对应的monitorexit。

关于monitorenter和monitorexit描述文字过多,大概流程看图说话
在这里插入图片描述

总结

通过上面的流程我们发现
1、synchronized是靠Monitor关联拿到锁的
2、如果竞争的时候拿不到锁,线程就去竞争队列
3、如果拿到锁了,第二次拿,它又拿到锁,其他线程进入阻塞队列
4、如果拿到锁的线程调用了wait方法,其他线程进入等待队列
5、释放锁,需要将计数器减减操作
6、出现异常,也释放锁。

这时又有问题,线程拿到锁干了什么?没拿到线程又在干什么??

通过查阅HotSpot之Monitor竞争源码可知

//锁竞争InterpreterRuntime::monitorenter
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");
if (UseBiasedLocking) {
// Retry fast entry if bias is revoked to avoid unnecessary inflation
ObjectSynchronizer::fast_enter(h_obj, elem->lock(), true, CHECK);
} else {
// 重量级锁,最终调用了objectMonitor.cpp中的ObjectMonitor::enter
ObjectSynchronizer::slow_enter(h_obj, elem->lock(), CHECK); 

通过这,我们了解到多线程进入InterpreterRuntime::monitorenter方法时,会进行锁竞争,没拿到锁的线程会进入竞争队列

//重量级锁入口
void ATTR ObjectMonitor::enter(TRAPS) {
Thread * const Self = THREAD ;
void * cur ;
// 1、通过CAS(原子操作)操作尝试把monitor的_owner字段设置为当前线程(开始竞争)
cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
if (cur == NULL) {
// Either ASSERT _recursions == 0 or explicitly set _recursions = 0.
assert (_recursions == 0 , "invariant") ;
assert (_owner == Self, "invariant") ;
// CONSIDER: set or assert OwnerIsThread == 1
return ;
}
// 2、拿到锁;计数+1,recursions++
if (cur == Self) {
_recursions ++ ;//第一次进入(计数+1)
return ;
}
if (Self->is_lock_owned ((address)cur)) {
assert (_recursions == 0, "internal state error");
_recursions = 1 ;
_owner = Self ;
OwnerIsThread = 1 ;
return ;
}
assert(Self->_Stalled == 0, "invariant") ;
Self->_Stalled = intptr_t(this) ;
if (Knob_SpinEarly && TrySpin (Self) > 0) {
assert (_owner == Self , "invariant") ;
assert (_recursions == 0 , "invariant") ;
assert (((oop)(object()))->mark() == markOopDesc::encode(this),
"invariant") ;
Self->_Stalled = 0 ;
return ;
}
assert (_owner != Self , "invariant") ;
assert (_succ != Self , "invariant") ;
assert (Self->is_Java_thread() , "invariant")
 ........................

总结:

拿到锁资源的线程干了以下这些事

  1. 通过CAS尝试把monitor的owner字段设置为当前线程。

  2. 记录当前线程的进入次数,当调用wait释放锁资源的时候,该线程可以再次申请拿锁,所以synchronized优化后,也是个可重入锁,执行recursions ++ ,记录重入的次数。

  3. 而获取锁失败的线程进入竞争队列进行等待

HotSpot源码之Monitor等待

//拿不到锁的线程他们都去干什么了??
void ATTR ObjectMonitor::EnterI (TRAPS) {
Thread * Self = THREAD ;
assert (Self->is_Java_thread(), "invariant") ;
assert (((JavaThread *) Self)->thread_state() == _thread_blocked ,
"invariant") ;
// 没拿到锁,还是要尝试TryLock一次
if (TryLock (Self) > 0) {
//拿到锁执行,在返回
assert (_succ != Self , "invariant") ;
assert (_owner == Self , "invariant") ;
assert (_Responsible != Self , "invariant") ;
return ;//成功获取
}
DeferredInitialize () ;
//没拿到锁,开始TrySpin自旋(CAS,while循环)
if (TrySpin (Self) > 0) {
assert (_owner == Self , "invariant") ;
assert (_succ != Self , "invariant") ;
assert (_Responsible != Self , "invariant") ;
return ;
}
..................... 

总结:

  1. 竞争失败的线程会封装为一个成ObjectWaiter对象node,状态设置成ObjectWaiter::TS_CXQ(竞争队列)

  2. 在for循环中,通过CAS进入竞争队列中

  3. 结点进入竞争队列会一直不断自旋拿锁,在并发环境下,持有锁线程的资源有可能会释放锁资源,如果仍然没拿到锁,就会死心,进行park休眠

  4. 当线程唤醒后,会从挂起的点进行ObjectMonitor::TryLock尝试获取锁

HotSpot之Monitor锁释放

1.调用ObjectMonitor::exit,将recursions减减操作,知道为0

2.为0后,唤醒竞争队列的线程

3.唤醒线程后,继续争夺锁,重复之前的步骤(锁竞争-----等待----释放

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值