1. Synchronized 锁的是什么?
我们都知道 Synchronized 是锁,而且是重量级锁,它保证原子性和可见性,但是不保证有序性。那么它到底锁的是什么?
答案很简单:锁的是对象,对象分为两种,一种是实例对象,一种是类的 Class 对象。有的人习惯把锁实例对象的称为对象锁,锁 Class 对象的称为类锁。其实类锁也是锁的对象,一种特殊的对象,类的 Class 对象。
- 对象锁
- synchronized(this|object)
- 修饰非静态方法
- 类锁
- synchronized(类.class)
- 修饰静态方法
1.1 synchronized (this)
下面我们测试对象锁的 synchronized (this) 。
1 | public class SynchronizedThisDemo implements Runnable { |
执行结果如下:
A enter
B enter
A exist
B exist
C enter
C exist
D enter
D exist
如结果所示, A、B 线程交替执行。因为 this 锁的是当前实例对象,而 A、B 线程的 SynchronizedThisDemo 对象不同,所以 A 在执行的时候并不能阻塞 B 的执行。
因为线程 C、D 使用的是同一个 SynchronizedThisDemo 对象,所以 D 线程被 C 线程阻塞了。
1.2 synchronized (object)
下面我们测试对象锁的 synchronized (object) 。
1 | public class SynchronizedObjectDemo implements Runnable{ |
执行结果:
A enter
A exist
B enter
B exist
C enter
C exist
D enter
D exist
因为从始至终加锁的对象只有一个——LOCK,所以当第一个线程 A 进来的时候就对 LOCK 进行加锁,后面的线程再进来就会被阻塞,直到 A 执行完毕释放锁。然后 B 加锁,释放锁,依次类推。
如果我们把 LOCK 对象放到 run 方法里面会怎样?如下面代码所示:
1 |
|
执行结果如下:
A enter
B enter
A exist
B exist
C enter
D enter
C exist
D exist
很明显,因为每个线程进 run 方法时,加锁的对象都不一样(每次都新建了一个),所以不会造成阻塞。
1.3 synchronized (类.class)
1 | public class SynchronizedClassDemo implements Runnable{ |
执行结果:
A enter
A exist
B enter
B exist
C enter
C exist
D enter
D exist
原因很简单,本质是类的 Class 对象有且仅有一个。类似于 synchronized (object) 的第一种情况。
普通方法的执行结果和 synchronized (this) 类似,如果是同一个对象,会串行;如果是多个对象,会交替执行。
静态方法和 synchronized (类.class) 类似,这里不做多余赘述。
有同学在使用过程中出现锁没生效的情况,多半是没搞清楚你加的锁到底锁的是什么。
2. 加锁原理
下面我们来看看,synchronized 加锁的原理是什么。
我们之前说过,synchronized 锁的是对象,无论是实例对象,还是类的 Class 对象。而每一个被加锁的对象都会关联一个 monitor 。monitor 有两个指令,一个是 monitorenter指令,另一个是 monitorexit指令。
monitorenter 指令是编译后插入到同步代码块的起始位置,monitorexit 指令是插入到结束位置或者异常处。
使用 Synchronized 的关键就是获取对象的 monitor,获取之后才能继续往下执行,否则就只能等待。而这个获取的过程是互斥的,即同一时刻只有一个线程能够获取到 monitor。执行同步代码块时先执行 monitorenter 指令,退出的时候 monitorexit 指令。
monitor 里面有一个计数器,从 0 开始计数。如果一个线程要获取 monitor 的锁,就看看计数器是不是 0,如果是,就可以获取锁了,然后对计数器加 1 。退出时对计数器减 1 。因为有计数器的存在,synchronized 也是可重入的。
下面我们来看看写的示例代码编译之后是不是有 monitorenter 和 monitorexit 。进入编译的目录,然后使用 javap -v 命令,如下所示:
javap -v SynchronizedClassDemo.class
结果如下,具体看加粗部分。
public void run();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=3, locals=4, args_size=1
0: ldc #2 // class com/common/concurrency/synchronize/SynchronizedClassDemo
2: dup
3: astore_1
4:monitorenter
5: iconst_0
6: istore_2
7: iload_2
8: iconst_5
9: if_icmpge 52
12: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
15: new #4 // class java/lang/StringBuilder
18: dup
19: invokespecial #5 // Method java/lang/StringBuilder.”
“:()V 22: invokestatic #6 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
25: invokevirtual #7 // Method java/lang/Thread.getName:()Ljava/lang/String;
28: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
31: ldc #9 // String get synchronized
33: invokevirtual #8 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
36: iload_2
37: invokevirtual #10 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
40: invokevirtual #11 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
43: invokevirtual #12 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
46: iinc 2, 1
49: goto 7
52: aload_1
53: monitorexit
54: goto 62
57: astore_3
58: aload_1
59: monitorexit
60: aload_3
61: athrow
62: return
Synchronized 关键字再往深了研究,就到硬件级别了,有兴趣的同学可以自行深入研究,推荐 《Java并发编程的艺术》一书。目前来说,知道怎么使用,加锁原理就够了。
参考资料:
《Java并发编程的艺术》