阅读此文你应该具备以下知识:
synchronized是什么
如果某一个资源被多个线程共享,为了避免因为资源抢占导致资源数据错乱,我们需要对线程进行同步,在Java中,synchronized
就是实现线程同步的关键字。
使用 synchronized
关键字,拿到 Java 对象的锁,保护锁定的代码块。JVM 保证同一时刻只有一个线程可以拿到这个 Java 对象的锁,执行对应的代码块,从而达到线程安全。
synchronized的使用
synchronized
关键字可以用来修饰三个地方:
- 修饰实例方法上,锁对象是当前的 this 对象。
- 修饰代码块,也就是
synchronized(object){}
,锁对象是()
中的对象,一般为this或明确的对象。 - 修饰静态方法上,锁对象是方法区中的类对象,是一个全局锁。
- 修饰类,即直接作用一个类。
针对synchronized
修饰的地方不同,实现的原理不同。
synchronized修饰实例方法
public class SyncTest {
public synchronized void sync(){
}
}
通过javap -verbose xxx.class
查看反编译结果:
从反编译的结果来看,我们可以看到sync()
方法中多了一个标识符。JVM就是根据该ACC_SYNCHRONIZED标识符来实现方法的同步,即:
当方法被执行时,JVM 调用指令会去检查方法上是否设置了ACC_SYNCHRONIZED标识符,如果设置了ACC_SYNCHRONIZED
标识符,则会获取锁对象的 monitor 对象,线程执行完方法体后,又会释放锁对象的 monitor对象。在此期间,其他线程无法获得锁对象的 monitor 对象。
synchronized修饰代码块
public class SyncTest {
private static int count;
public SyncTest() {
count = 0;
}
public void sync() {
synchronized (this) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
SyncTest s = new SyncTest();
Thread t0 = new Thread(new Runnable() {
@Override
public void run() {
s.sync();
}
});
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
s.sync();
}
});
t0.start();
t1.start();
}
}
输出:
Thread-0:0
Thread-0:1
Thread-0:2
Thread-0:3
Thread-0:4
Thread-1:5
Thread-1:6
Thread-1:7
Thread-1:8
Thread-1:9
很明显,线程 1 要等到线程 0 执行完之后才会开始执行。再去查看字节码信息:
我们可以看到sync()
字节码指令中会有两个monitorenter和monitorexit指令:
-
monitorenter: 该指令表示获取锁对象的 monitor 对象,这时 monitor 对象中的 count 会加+1,如果 monitor 已经被其他线程所获取,该线程会被阻塞住,直到 count = 0,再重新尝试获取monitor对象。
-
monitorexit: 该指令表示该线程释放锁对象的 monitor 对象,这时monitor对象的count便会-1变成0,其他被阻塞的线程可以重新尝试获取锁对象的monitor对象。
synchronized修饰静态方法
public class SyncTest {
private static int count;
public SyncTest() {
count = 0;
}
public synchronized static void sync() {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
Thread.sleep(100);
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
SyncTest s0 = new SyncTest();
SyncTest s1 = new SyncTest();
Thread t0 = new Thread(new Runnable() {
@Override
public void run() {
s0.sync();
}
});
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
s1.sync();
}
});
t0.start();
t1.start();
}
}
测试结果:
Thread-0:0
Thread-0:1
Thread-0:2
Thread-0:3
Thread-0:4
Thread-1:5
Thread-1:6
Thread-1:7
Thread-1:8
Thread-1:9
我们知道静态方法是属于类的而不属于对象的。同样,synchronized 修饰的静态方法锁定的是这个类的所有对象。因此,尽管是s0
和s1
是2个不同的对象,但在t1
和t2
并发执行时却保持了线程同步,就是因为sync()
是静态方法,而静态方法是属于类的,所以s0
和s1
相当于用了同一把锁。
再去看看字节码信息:
可以看到跟放在实例方法相同,也是sync()
方法上会多一个标识符。可以得出synchronized放在实例方法上和放在静态方法上的实现原理相同,都是ACC_SYNCHRONIZED标识符去实现的。只是它们锁住的对象不同。
synchronized修饰类
public class SyncTest {
private static int count;
public SyncTest() {
count = 0;
}
public void sync() {
synchronized (SyncTest.class) {
for (int i = 0; i < 5; i++) {
try {
System.out.println(Thread.currentThr