关键字: synchronized详解
Synchronized的使用
在应用Synchronized关键字时需要把握如下注意点:
- 一把锁只能同时被一个线程获取,没有获得锁的线程只能等待;
- 每个实例都对应有一个自己的一把锁(this),不同实例之间互不影响;例如:锁对象是*.class以及Synchronized修饰的是static方法时,所有对象共用同一把锁;
- Synchronized修饰的方法,无论方法正常执行完毕还是异常,都会释放锁;
对象锁
包括方法锁(默认的锁对象为this),同步代码块锁(自己指定锁对象)
代码块形式
- 示例1:
public class SyncObjectLock implements Runnable{
private static SyncObjectLock syncObjectLock = new SyncObjectLock();
@Override
public void run() {
synchronized (syncObjectLock) {
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 thread1 = new Thread(syncObjectLock);
Thread thread2 = new Thread(syncObjectLock);
thread1.start();
thread2.start();
}
}
输出结果:
我是线程Thread-0
线程Thread-0结束
我是线程Thread-1
线程Thread-1结束
- 示例2:
public class SyncObjectLock2 implements Runnable{
private static SyncObjectLock2 instance = new SyncObjectLock2();
// 创建2把锁
Object block1 = new Object();
Object block2 = new Object();
@Override
public void run() {
synchronized (block1){
System.out.println("我是block1,线程"+ Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是block1,线程"+ Thread.currentThread().getName() + "结束");
}
synchronized (block2){
System.out.println("我是block2,线程"+ Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是block2,线程"+ Thread.currentThread().getName() + "结束");
}
}
public static void main(String[] args) {
Thread thread1 = new Thread(instance);
Thread thread2 = new Thread(instance);
thread1.start();
thread2.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结束
方法锁形式:synchronized修饰普通方法,锁对象默认为this
public class SyncObjectLock3 implements Runnable{
private static SyncObjectLock3 instance = new SyncObjectLock3();
@Override
public void run() {
method();
}
private 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 thread1 = new Thread(instance);
Thread thread2 = new Thread(instance);
thread1.start();
thread2.start();
}
}
输出结果:
我是线程Thread-0
线程Thread-0结束
我是线程Thread-1
线程Thread-1结束
synchronized指定锁对象为class对象
public class SyncClassLock implements Runnable{
private static SyncClassLock instance = new SyncClassLock();
@Override
public void run() {
synchronized (SyncClassLock.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 thread1 = new Thread(instance);
Thread thread2 = new Thread(instance);
thread1.start();
thread2.start();
}
}
输出结果:
我是线程Thread-0
线程Thread-0结束
我是线程Thread-1
线程Thread-1结束
Synchronized原理分析
加锁和释放锁的原理
现象、时机(内置锁this)、深入JVM看字节码(反编译看monitor命令)
深入JVM看字节码,生成如下代码:
public class SyncDemo1 {
private Object obj;
public SyncDemo1(Object obj) {
this.obj = obj;
}
public void run(){
synchronized (obj){
}
method();
}
public static void method(){
}
}
使用javac命令生成class文件:
javac SyncDemo1.java
使用javap命令反编译查看class信息:
javap -verbose SyncDemo1.class
得到如下信息:
public void run();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: getfield #2 // Field obj: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 #3 // Method method:()V
20: return
Exception table:
from to target type
7 9 12 any
12 15 12 any
LineNumberTable:
关注monitorenter
、monitorexit
指令即可。
Monitorenter
、Monitorexit
指令,会让对象在执行,使其锁计数器加1或减1。每一个对象在同一时间只与一个monitor
(锁)相关联,而monitor
在同一时间只能被一个线程获得,一个对象在尝试获得与这个对象相关联的monitor
锁所有权的时候,monitorenter
指令会发生如下三种情况之一:
- monitor计数器为0,意味着目前没有被线程获得,那么被线程获得之后然后把计数器+1,一旦加1,别的线程想获得就只能等待;
- 如果这个monitor已经拿到了这个锁的所有权,又重入了这把锁,那么计数器就会累加变成2,并且随着重入次数,会一直累加;
- 这把锁已经被别的线程获取了,等待锁释放;
monitorexit
指令:释放对于monitor的所有权,释放过程很简单,就是将monitor的计数器减1,如果减完之后计数器不是0,则代表刚才时重入进来的,当前线程还继续持有这把锁的所有权,如果计数器变成0,则代表线程不再拥有该monitor的所有权,即释放锁。
可重入原理:加锁次数计数器
- 什么是可重入?可重入锁?
可重入:(维基百科)若一个程序或子程序可以“在任意时刻被中断然后操作系统调度执行另外一段程序,这段程序又屌用了该子程序不会出错”,则称其为可重入(reentrant获取re-entrant)的。即当该子程序正在运行时,执行线程可以再次进入并执行它,仍然获得符合设计时预期结果,与多线程并发执行的线程安全不同,可重入强调对单个线程执行时重新进入同一个子程序仍然时安全的。
可重入锁:又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。
例子:
public class SynchronizedDemo {
public static void main(String[] args) {
SynchronizedDemo demo = new SynchronizedDemo();
demo.method();
}
private synchronized void method(){
System.out.println(Thread.currentThread().getId()+": method()");
method1();
}
private synchronized void method1(){
System.out.println(Thread.currentThread().getId()+": method1()");
method2();
}
private synchronized void method2(){
System.out.println(Thread.currentThread().getId()+": method2()");
method3();
}
private synchronized void method3(){
System.out.println(Thread.currentThread().getId()+": method3()");
}
}
结合前文中加锁和释放锁的原理,不难理解:
-
执行monitorenter获取锁
monitor计数器=0,可获取锁
执行method()方法,monitor计数器+1 ->1 (获取到锁)
执行method1()方法,monitor计数器+1 ->2
执行method2()方法,monitor计数器+1 ->3
执行method3()方法,monitor计数器+1 ->4 -
执行monitorexit指令
method3()方法执行完,monitor计数器-1 ->3
method2()方法执行完,monitor计数器-1 ->2
method1()方法执行完,monitor计数器-1 ->1
method()方法执行完,monitor计数器-1 -> 0 (释放锁)
这就是Synchronized的重入性,即在同一锁程中,每个对象拥有一个monitor计数器,当线程获取该对象锁之后,monitor计数器就会加1,释放锁后将monitor计数器减一,线程不需要再次获取同一把锁。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义