1. 是什么?
synchronized 是 JAVA 关键字,用在方法或代码块。控制方法或代码同一时间只有一个线程执行,用来解决多线程同时访问出现的并发问题。
2. 使用分类(作用域)
对象锁(代码块)
- 表现形式1:加在方法上
private synchronized boolean sellTicketsWithSyncMethod(int count) {
if (totalTickets - count < 0) {
return false;
} else {
totalTickets = totalTickets - count;
return true;
}
}
- 表现形式2:加在对象上
锁多个对象时,相互不干扰
private final Object LOCK = new Object();
private boolean sellTicketsWithSyncObject(int count) {
synchronized (LOCK) {
if (totalTickets - count < 0) {
return false;
} else {
totalTickets = totalTickets - count;
return true;
}
}
}
- 表现形式3:this当前对象上
private boolean sellTicketsWithSyncObject(int count) {
synchronized (this) {
if (totalTickets - count < 0) {
return false;
} else {
totalTickets = totalTickets - count;
return true;
}
}
}
类锁
- 表现形式1:static
public static synchronized void staticMethod() {
// todo
}
- 表现形式2:synchronized(xxx.class)
public Class SyncClass {
private void method () {
synchronized (SyncClass.class) {
//todo
}
}
}
对象锁和类锁区别?
对象锁锁的是一个对象。不同对象下互不干扰。而类只有一个。
3. synchronized 的特征
- 它是一个可重入锁。
- JAVA 语言的关键字,由虚拟机字节码指令实现。(下面深入讲解)
- 用 monitor 来控制进出
- moniterenter 进入
- Monitorexit 为退出
- 不可中断,无法控制锁的超时时间,可能会造成阻塞。而 ReentranLock 可以,这个后面讲解。
- synchronized 只有非公平锁。 公平锁和非公平锁的区别
- 粒度是对象,锁的是对象而非变量。
3.1 可重入讲解
指的是同一线程的外层函数获得锁之后,内层函数可以直接再次获取该锁。从而避免死锁。
演示:
public class SynchronizedReentrantTest {
int i = 0;
public synchronized void method1() {
method2();
}
private synchronized void method2() {
}
private synchronized void reentrantSelf() {
if (i == 0) {
i++;
reentrantSelf();
}
}
public static void main(String[] args) {
SynchronizedReentrantTest instance = new SynchronizedReentrantTest();
// 重入其他方法
instance.method1();
// 自己重入自己
instance.reentrantSelf();
// 当然也可以重入其他类方法,这里不演示了。
}
}
4. 深入理解 synchronized (反编译来看看)
如何反编译?
- 编译
javac xxxx.java
- 反编译
javap -c -p -v xxxx.class
以下是截取的部分源码:
public class SynchronizedMethodDif {
//...
// 作用于对象上
private static final Object LOCK = new Object();
private static boolean sellTicketsWithSyncObject(int count) {
synchronized (LOCK) {
if (totalTickets - count < 0) {
return false;
} else {
totalTickets = totalTickets - count;
return true;
}
}
}
// 作用于 static 方法上
private static synchronized void soutStatic() {
System.out.println(Thread.currentThread().getName() + " is sleeping ");
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " : 1");
System.out.println(Thread.currentThread().getName() + " finish sleeping ");
}
// 作用于普通方法上
private synchronized void soutNonStatic() {
System.out.println(Thread.currentThread().getName() + " is sleeping ");
try {
Thread.sleep(3000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " : 1");
System.out.println(Thread.currentThread().getName() + " finish sleeping ");
}
//...
}
反编译后:
// 作用于对象上
private static boolean sellTicketsWithSyncObject(int);
descriptor: (I)Z
flags: (0x000a) ACC_PRIVATE, ACC_STATIC
Code:
stack=2, locals=3, args_size=1
0: getstatic #10 // Field LOCK:Ljava/lang/Object;
3: dup
4: astore_1
5: monitorenter
...
16: monitorexit
17: ireturn
...
27: aload_1
28: monitorexit
29: ireturn
...
31: aload_1
32: monitorexit
33: aload_2
34: athrow
...
frame_type = 75 /* same_locals_1_stack_item */
stack = [ class java/lang/Throwable ]
...
// 作用于 static 上
private static synchronized void soutStatic();
descriptor: ()V
flags: (0x002a) ACC_PRIVATE, ACC_STATIC, ACC_SYNCHRONIZED
Code:
stack=2, locals=1, args_size=0
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
...
frame_type = 4 /* same */
...
// 作用于 普通方法 上
private synchronized void soutNonStatic();
descriptor: ()V
flags: (0x0022) ACC_PRIVATE, ACC_SYNCHRONIZED
Code:
stack=2, locals=2, args_size=1
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
...
frame_type = 4 /* same */
4.1 原理分析
-> 作用于 代码块(对象) 上:
通过 monitor 来控制,使用monitorenter 和 monitorexit 指令控制线程进出。
monitor 讲解:
是一个监视器,执行是依赖于对象的锁计数
-
monitorenter
:每个对象都是一个监视器锁(monitor)。当monitor被占用时就会处于锁定状态,线程执行monitorenter指令时尝试获取monitor的所有权,过程如下:- 如果monitor的进入数为0,则该线程进入monitor,然后将进入数设置为1,该线程即为monitor的所有者
- 如果线程已经占有该monitor,只是重新进入,则进入monitor的进入数加1
- 如果其他线程已经占用了monitor,则该线程进入阻塞状态,直到monitor的进入数为0,再重新尝试获取monitor的所有权
-
monitorexit
:执行monitorexit的线程必须是objectref所对应的monitor的所有者。指令执行时,monitor的进入数减1,如果减1后进入数为0,那线程退出monitor,不再是这个monitor的所有者。其他被这个monitor阻塞的线程可以尝试去获取这个 monitor 的所有权。
-> 作用于 方法 上
- 方法使用 ACC_SYNCHRONIZED 标识
- static 方法,则锁是作用在类上
- 非 static 方法,则锁作用在类对象上
网友评论