synchronized是Java中的关键字,是一种同步锁。它可以保证方法或者代码块在运行时,同一时刻只有一个线程可以访问,同时它还可以保证共享变量的内存可见性,和操作的原子性。
它修饰的对象有以下几种:
-
修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
-
修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;
虽然可以使用synchronized来定义方法,但synchronized并不属于方法定义的一部分,因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。
- 修饰一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
- 修饰一个类,其作用的范围是synchronized后面括号括起来的部分,作用的对象是这个类的所有对象。
Java中的每一个对象都可以作为锁。具体表现为以下3种形式。
普通同步方法,锁是当前实例对象
静态同步方法,锁是当前类的 class 对象
同步代码块,锁是Synchonized 括号里面的对象
底层原理Java 中每一个对象都可以作为锁,是因为每个对象都有一个监视器锁(monitor)。
当监视器锁的已经有持有者了,
表示锁已经被其他线程获取
当监视器锁的持有者为null时,
表示可以获取锁
3当线程获取锁失败后,则该线程进入阻塞状态,直到监视器锁的持有者为null,再重新尝试获取监视器锁。
总结
synchronized 是重量级锁,在 JDK1.6 中进行优化,如锁升级、锁消除、锁粗化等技术来减少锁操作的开销。1.8之后可以等同ReentrantLock的使用
管程管程 (英语:Monitor,也称为监视器) 是一种程序结构,结构内的多个子程序(对象或模块)形成的多个工作线程互斥访问共享资源。
Monitor是操作系统层面的,在Java层面是看不到的
这些共享资源一般是硬件设备或一群变量。对共享变量能够进行的所有操作集中在一个模块中。(把信号量及其操作原语“封装”在一个对象内部)管程实现了在一个时间点,最多只有一个线程在执行管程的某个子程序。管程提供了一种机制,管程可以看做一个软件模块,它是将共享的变量和对于这些共享变量的操作封装起来,形成一个具有一定接口的功能模块,进程可以调用管程来实现进程级别的并发控制。
每个 Java 对象都可以关联一个 Monitor 对象,如果使用 synchronized 给对象上锁(重量级)之后,该对象头的 Mark Word 中就被设置指向 Monitor 对象的指针
Monitor 结构如下
刚开始 Monitor 中 Owner 为 null 当 Thread-2 执行 synchronized(obj) 获取锁,就会将 Monitor 的所有者 Owner 置为 Thread-2,Monitor中只能有一个 Owner,即 Thread-2是obj对象对应的Monitor 的持有者 在 Thread-2 上锁的过程中,如果 Thread-3,Thread-4,Thread-5 也来执行 synchronized(obj),发现已经有其他线程关联了obj对象的Monitor 的Owner,就会进入EntryList(同步队列) BLOCKED阻塞状态 Thread-2 执行完同步代码块的内容,然后唤醒 EntryList 中等待的线程来竞争锁,竞争时是非公平的 图中 WaitSet 中的 Thread-0,Thread-1 是之前获得过锁,但条件不满足(调用了wait()方法)进入 WAITING 状态的线程,
注意: synchronized 必须是进入同一个对象的 monitor 才有上述的效果(每个对象都有一个monitor ) 不加 synchronized 的对象不会关联监视器,不遵从以上规则
售票案例实现3个售票员卖出100张票的案例
分析:线程 操作 资源类
第一 创建资源类
第二 创建多线程调用资源类的方法
票就是资源类,它的属性就是有多少张,操作方法就是卖出
3个售票员就是创建三个线程
不进行同步方案package com.dongguo.concurrent.synchronize;
/**
* @author Dongguo
* @date 2021/9/3 0003-10:14
* @description: 实现3个售票员卖出100张票的案例
* 没有使用同步锁,存在超卖问题
*/
public class SaleTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(() -> {
//循环100次保证能够卖光票
for (int i = 0; i {
for (int i = 0; i {
for (int i = 0; i 0) {
//为了体现超卖现象,线程休眠100ms
Thread.sleep(100);
count--;
System.out.println(Thread.currentThread().getName() + "卖票成功,还剩" + count + "张票!");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
number被三个线程共享,同时调用saleTicket方法
发现问题:票重复卖出, 顺序混乱,卖出超过100张票
使用synchronized解决并发导致超卖的问题 同步方法package com.dongguo.concurrent.synchronize;
/**
* @author Dongguo
* @date 2021/9/3 0003-10:14
* @description: 实现3个售票员卖出100张票的案例
* 使用同步方法,不存在超卖问题
*/
public class SaleTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(() -> {
//循环100次保证能够卖光票
for (int i = 0; i {
for (int i = 0; i {
for (int i = 0; i 0) {
//为了体现超卖现象,线程休眠100ms
Thread.sleep(100);
count--;
System.out.println(Thread.currentThread().getName() + "卖票成功,还剩" + count + "张票!");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
在循环的方法上添加 synchronized ,
虽然线程排队打印,运行没有发生超卖问题,但是运行效率比较低
同步代码块package com.dongguo.concurrent.synchronize;
/**
* @author Dongguo
* @date 2021/9/3 0003-10:14
* @description: 实现3个售票员卖出100张票的案例
* 使用同步代码块,不存在超卖问题
*/
public class SaleTicket {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(() -> {
//循环100次保证能够卖光票
for (int i = 0; i {
for (int i = 0; i {
for (int i = 0; i 0) {
//为了体现超卖现象,线程休眠100ms
Thread.sleep(100);
count--;
System.out.println(Thread.currentThread().getName() + "卖票成功,还剩" + count + "张票!");
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
速度些许提升也没有发生乱序重复的现象
如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况: 1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有; 2)线程执行发生异常,此时JVM会让线程自动释放锁。 那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。 因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。
使用Synchronized时需要注意什么?1.Synchronized使用时需要注意的地方锁对象不能为空。
锁对象的信息是保留在对象头中的,如果对象为空,则锁的信息也就不存在了。
2.作用域不宜过大
synchronized代码块的代码量不宜过多,如果把过多的代码放在其中,程序的运行会变为串行,速度会下降。各个线程并行可以提高效率,我们应该仅把那些影响线程安全的代码,放入synchronized代码块中,串行执行;不需要考虑线程安全的代码,并行执行,达到效率最高。
3.避免死锁
避免让线程对锁持有并等待的情况出现
阿里开发手册
【强制】高并发时,同步调用应该去考量锁的性能损耗。能用无锁数据结构,就不要用锁;能·锁区块,就不要锁整个方法体;能用对象锁,就不要用类锁。说明:尽可能使加锁的代码块工作量尽可能的小,避免在锁代码块中调用RPC方法。
从字节码角度分析synchronized实现查看字节码命令javap
javap命令反汇编一个或多个类文件。输出取决于所使用的选项。当不使用任何选项时,javap命令将打印包、受保护字段和公共字段以及传递给它的类的方法。javap命令将其输出打印到stdout。
javap -c xxx.class
-c
为类中的每个方法打印反汇编代码,例如,组成Java字节码的指令,即 对代码进行反汇编
javap -v xxx.class
-v
即**-verbose**
输出附加信息 比如打印方法的参数,行号、本地变量表,反汇编等详细信息
同步代码块package com.dongguo.synclock;
/**
* @author Dongguo
* @date 2021/9/5 0005-21:23
* @description: 字节码角度分析synchronized
*/
public class LockByteCodeDemo {
Object object = new Object();
//同步代码块
public void m1() {
synchronized (object) {
System.out.println("同步代码块");
}
}
public static void main(String[] args) {
}
}
经过编译
IDEA 中 Build ->Build Project
查看target生成的LockByteCodeDemo.class文件右键 OPen In -> Terminal
输入命令
javap -v LockByteCodeDemo.class
输出信息
public void m1();
descriptor: ()V
flags: 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 //将lock引用复制一份存到slot1中
6: monitorenter //将lock对象的markword指针指向Monitor 即加锁
7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
10: ldc #5 // String 同步代码块
12: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
15: aload_1 //从slot1中获得临时的lock引用
16: monitorexit //将lock对象的makrword重置,唤醒等待队列EntryList的线程
17: goto 25 //调到25行
20: astore_2 //e 将异常e存到slot2中
21: aload_1 //从slot1中获得临时的lock引用
22: monitorexit //解锁
23: aload_2 //从slot2中获得异常e
24: athrow //抛出异常e
25: return
Exception table:
from to target type
7 17 20 any //监控7-17行 如果出现异常指向20行
20 23 20 any
LineNumberTable:
line 14: 0
line 15: 7
line 16: 15
line 17: 25
LocalVariableTable:
Start Length Slot Name Signature
0 26 0 this Lcom/dongguo/synclock/LockByteCodeDemo;
StackMapTable: number_of_entries = 2
frame_type = 255 /* full_frame */
offset_delta = 20
locals = [ class com/dongguo/synclock/LockByteCodeDemo, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
frame_type = 250 /* chop */
offset_delta = 4
synchronized同步代码块实现使用的是monitorenter和monitorexit指令
当一个线程进入同步代码块时,它使用monitorenter指令请求进入。如果当前对象的监视器计数器为0,则它会被准许进入,若为1,则判断持有当前监视器的线程是否为自己,如果是,则进入,否则进行等待,直到对象的监视器计数器为0,才会被允许进入同步块。当线程退出同步块时,需要使用monitorexit声明退出。
在Java虚拟机中,任何对象都有一个监视器与之相关联,用来判断对象是否被锁定,当监视器被持有后,对象处于锁定状态。指令monitorenter和monitorexit在执行时,都需要在操作数栈顶压入对象,之后monitorenter和monitorexit的锁定和释放都是针对这个对象的监视器进行的。下图展示了监视器如何保护临界区代码不同时被多个线程访问,只有当线程4离开临界区后,线程1、2、3才有可能进入。
编译器必须确保无论方法通过何种方式完成,方法中调用过的每条monitorenter指令都必须执行其对应的monitorexit指令,而无论这个方法是正常结束还是异常结束。为了保证在方法异常完成时monitorenter和monitorexit指令依然可以正确配对执行,编译器会自动产生一个异常处理器,这个异常处理器声明可处理所有的异常,它的目的就是用来执行monitorexit指令
monitorenter:monitorexit 为 1:2 why???
因为第一个monitorexit正常完成解锁
第二个monitorexit保证出现Exception或者Error也能解锁,
确保一定能够解锁 不会发生死锁
那么一定是一个enter两个exit吗?
m1方法里面自己添加一个异常试试
package com.dongguo.synclock;
/**
* @author Dongguo
* @date 2021/9/5 0005-21:23
* @description: 字节码角度分析synchronized
*/
public class LockByteCodeDemo {
Object object = new Object();
//同步代码块
public void m1() {
synchronized (object) {
System.out.println("同步代码块");
//抛出异常
throw new RuntimeException("出现异常");
}
}
public static void main(String[] args) {
}
}
字节码
public void m1();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=3, args_size=1
0: aload_0
1: getfield #3 // Field object:Ljava/lang/Object;
4: dup
5: astore_1
6: monitorenter
7: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
10: ldc #5 // String 同步代码块
12: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
15: new #7 // class java/lang/RuntimeException
18: dup
19: ldc #8 // String 出现异常
21: invokespecial #9 // Method java/lang/RuntimeException."":(Ljava/lang/String;)V
24: athrow
25: astore_2
26: aload_1
27: monitorexit
28: aload_2
29: athrow
Exception table:
from to target type
7 28 25 any
LineNumberTable:
line 14: 0
line 15: 7
line 17: 15
line 18: 25
LocalVariableTable:
Start Length Slot Name Signature
0 30 0 this Lcom/dongguo/synclock/LockByteCodeDemo;
StackMapTable: number_of_entries = 1
frame_type = 255 /* full_frame */
offset_delta = 25
locals = [ class com/dongguo/synclock/LockByteCodeDemo, class java/lang/Object ]
stack = [ class java/lang/Throwable ]
只有一个monitorenter和一个monitorexit
athrow了两次
确保一定能够抛出异常
普通同步方法package com.dongguo.synclock;
/**
* @author Dongguo
* @date 2021/9/5 0005-21:23
* @description: 字节码角度分析synchronized
*/
public class LockByteCodeDemo {
Object object = new Object();
//同步代码块
// public void m1() {
// synchronized (object) {
// System.out.println("同步代码块");
// //抛出异常
// throw new RuntimeException("出现异常");
// }
// }
//普通同步方法
public synchronized void m2() {
System.out.println("普通同步方法");
}
public static void main(String[] args) {
}
}
字节码
public synchronized void m2();
descriptor: ()V
flags: ACC_PUBLIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=1
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #5 // String 普通同步方法
5: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 22: 0
line 23: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 this Lcom/dongguo/synclock/LockByteCodeDemo;
普通同步方法没有monitorenter和monitorexit指令
但是flags有一个ACC_SYNCHRONIZED标志
方法级的同步:是隐式的, 即无须通过字节码指令来控制,它实现在方法调用和返回操作之中。虚拟机可以从方法常量池的方法表结构中的ACC-SYNCHRONIZED访问标志得知一个方法是否声明为同步方法;当调用方法时,调用指令将会检查方法的ACC-SYNCHRONIZED访问标志是否设置。如果设置了,执行线程将先持有同步锁,然后执行方法。最后在方法完成(无论是正常完成还是非正常完成)时释放同步锁。在方法执行期间,执行线程持有了同步锁,其他任何线程都无法再获得同一个锁。如果一个同步方法执行期间抛出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的锁将在异常抛到同步方法之外时自动释放。
没有使用monitorenter和monitorexit进行同步区控制。这是因为,对于同步方法而言,当虚拟机通过方法的访问标示符判断是一个同步方法时,会自动在方法调用前进行加锁,当同步方法执行完毕后,不管方法是正常结束还是有异常抛出,均会由虚拟机释放这个锁。因此,对于同步方法而言, monitorenter和monitorexit指令是隐式存在的,并未直接出现在字节码中。
从JVM规范中可以看到Synchonized在JVM里的实现原理,JVM基于进入和退出Monitor对 象来实现方法同步和代码块同步,但两者的实现细节不一样。代码块同步是使用monitorenter 和monitorexit指令实现的,而方法同步是使用另外一种方式实现的,细节在JVM规范里并没有详细说明。但是,方法的同步同样可以使用这两个指令来实现。
monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结 束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之配对。任何对象都有 一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter 指令时,将会尝试获取对象所对应的monitor的所有权,即尝试获得对象的锁。
静态同步方法package com.dongguo.synclock;
/**
* @author Dongguo
* @date 2021/9/5 0005-21:23
* @description: 字节码角度分析synchronized
*/
public class LockByteCodeDemo {
Object object = new Object();
//同步代码块
// public void m1() {
// synchronized (object) {
// System.out.println("同步代码块");
// //抛出异常
// throw new RuntimeException("出现异常");
// }
// }
//普通同步方法
// public synchronized void m2() {
// System.out.println("普通同步方法");
// }
//静态同步方法
public static synchronized void m3() {
System.out.println("静态同步方法");
}
public static void main(String[] args) {
}
}
字节码
public static synchronized void m3();
descriptor: ()V
flags: ACC_PUBLIC, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=0, args_size=0
0: getstatic #4 // Field java/lang/System.out:Ljava/io/PrintStream;
3: ldc #5 // String 静态同步方法
5: invokevirtual #6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
8: return
LineNumberTable:
line 26: 0
line 27: 8
此时的flags 多了ACC_STATIC,ACC_SYNCHRONIZED标志
ACC_STATIC, ACC_SYNCHRONIZED访问标志区分该方法是否是静态同步方法
为什么每一个对象都可以成为一个锁?Monitor可以理解为一种同步工具,也可理解为一种同步机制,常常被描述为一个Java对象。Java对象是天生的Monitor,每一个Java对象都有成为Monitor的潜质,因为在Java的设计中 ,每一个Java对象自打娘胎里出来就带了一把看不见的锁,它叫做内部锁或者Monitor锁。
Java 中每一个对象都可以作为锁,是因为每个对象都有一个监视器锁(monitor)。对象都继承Object类
在HotSpot虚拟机中,monitor采用ObjectMonitor实现
objectMonitor.hpp
ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0;
_object = NULL;
_owner = NULL;
_WaitSet = NULL;
ObjectMonitor中有几个关键属性 _owner 指向持有ObjectMonitor对象的线程 _WaitSet 存放处于wait状态的线程队列 _EntryList 存放处于等待锁block状态的线程队列 _recursions 锁的重入次数 _count 用来记录该线程获取锁的次数
Monitor与java对象以及线程是如何关联 ? 1.如果一个java对象被某个线程锁住,则该java对象的Mark Word字段中LockWord指向monitor的起始地址 2.Monitor的Owner字段会存放拥有相关联对象锁的线程id
Mutex LockMonitor是在jvm底层实现的,底层代码是c++。本质是依赖于底层操作系统的Mutex Lock实现,操作系统实现线程之间的切换需要从用户态到内核态的转换,状态转换需要耗费很多的处理器时间成本非常高。所以synchronized是Java语言中的一个重量级操作。
mutex.hpp
private:
int TrySpin (Thread * Self) ;
int TryLock () ;
int TryFast () ;
int AcquireOrPush (ParkEvent * ev) ;
void IUnlock (bool RelaxAssert) ;
void ILock (Thread * Self) ;
int IWait (Thread * Self, jlong timo);
int ILocked () ;
protected:
static void ClearMonitor (Monitor * m, const char* name = NULL) ;
Monitor() ;
public:
Monitor(int rank, const char *name, bool allow_vm_block=false);
~Monitor();
// Wait until monitor is notified (or times out).
// Defaults are to make safepoint checks, wait time is forever (i.e.,
// zero), and not a suspend-equivalent condition. Returns true if wait
// times out; otherwise returns false.
bool wait(bool no_safepoint_check = !_no_safepoint_check_flag,
long timeout = 0,
bool as_suspend_equivalent = !_as_suspend_equivalent_flag);
bool notify();
bool notify_all();
void lock(); // prints out warning if VM thread blocks
void lock(Thread *thread); // overloaded with current thread
void unlock();
bool is_locked() const { return _owner != NULL; }
bool try_lock(); // Like lock(), but unblocking. It returns false instead
// Lock without safepoint check. Should ONLY be used by safepoint code and other code
// that is guaranteed not to block while running inside the VM.
void lock_without_safepoint_check();
void lock_without_safepoint_check (Thread * Self) ;
// Current owner - not not MT-safe. Can only be used to guarantee that
// the current running thread owns the lock
Thread* owner() const { return _owner; }
bool owned_by_self() const;
// Support for JVM_RawMonitorEnter & JVM_RawMonitorExit. These can be called by
// non-Java thread. (We should really have a RawMonitor abstraction)
void jvm_raw_lock();
void jvm_raw_unlock();
const char *name() const { return _name; }
void print_on_error(outputStream* st) const;
#ifndef PRODUCT
void print_on(outputStream* st) const;
void print() const { print_on(tty); }
debug_only(int rank() const { return _rank; })
bool allow_vm_block() { return _allow_vm_block; }
debug_only(Monitor *next() const { return _next; })
debug_only(void set_next(Monitor *next) { _next = next; })
#endif
void set_owner(Thread* owner) {
#ifndef PRODUCT
set_owner_implementation(owner);
debug_only(void verify_Monitor(Thread* thr));
#else
_owner = owner;
#endif
}
};
mutex.cpp
void Monitor::lock (Thread * Self) {
#ifdef CHECK_UNHANDLED_OOPS
// Clear unhandled oops so we get a crash right away. Only clear for non-vm
// or GC threads.
if (Self->is_Java_thread()) {
Self->clear_unhandled_oops();
}
#endif // CHECK_UNHANDLED_OOPS
debug_only(check_prelock_state(Self));
assert (_owner != Self , "invariant") ;
assert (_OnDeck != Self->_MutexEvent, "invariant") ;
if (TryFast()) {
Exeunt:
assert (ILocked(), "invariant") ;
assert (owner() == NULL, "invariant");
set_owner (Self);
return ;
}
// The lock is contended ...
bool can_sneak = Self->is_VM_thread() && SafepointSynchronize::is_at_safepoint();
if (can_sneak && _owner == NULL) {
// a java thread has locked the lock but has not entered the
// critical region -- let's just pretend we've locked the lock
// and go on. we note this with _snuck so we can also
// pretend to unlock when the time comes.
_snuck = true;
goto Exeunt ;
}
// Try a brief spin to avoid passing thru thread state transition ...
if (TrySpin (Self)) goto Exeunt ;
check_block_state(Self);
if (Self->is_Java_thread()) {
// Horribile dictu - we suffer through a state transition
assert(rank() > Mutex::special, "Potential deadlock with special or lesser rank mutex");
ThreadBlockInVM tbivm ((JavaThread *) Self) ;
ILock (Self) ;
} else {
// Mirabile dictu
ILock (Self) ;
}
goto Exeunt ;
}
void Monitor::lock() {
this->lock(Thread::current());
}
那用户态和内核态又是啥呢?
Linux系统的体系结构大家大学应该都接触过,分为用户空间(应用程序的活动空间)和内核。
我们所有的程序都在用户空间运行,进入用户运行状态也就是(用户态),但是很多操作可能涉及内核运行,比如I/O,我们就会进入内核运行状态(内核态)。
这个过程是很复杂的,也涉及很多值的传递,我简单概括下流程:
- 用户态把一些数据放到寄存器,或者创建对应的堆栈,表明需要操作系统提供的服务。
- 用户态执行系统调用(系统调用是操作系统的最小功能单位)。
- CPU切换到内核态,跳到对应的内存指定的位置执行指令。
- 系统调用处理器去读取我们先前放到内存的数据参数,执行程序的请求。
- 调用完成,操作系统重置CPU为用户态返回结果,并执行下个指令。
所以大家一直说,1.6之前是重量级锁,没错,但是他重量的本质,是ObjectMonitor调用的过程,以及Linux内核的复杂运行机制决定的,大量的系统资源消耗,所以效率才低。
参考
死磕synchronized底层实现