1、synchronized 的使用小例子?

  1. public class SynchronizedTest {
  2. public static volatile int race = 0;
  3. private static CountDownLatch countDownLatch = new CountDownLatch(2);
  4. public static void main(String[] args) throws InterruptedException {
  5. // 循环开启2个线程来计数
  6. for (int i = 0; i < 2; i++) {
  7. new Thread(() -> {
  8. // 每个线程累加1万次
  9. for (int j = 0; j < 10000; j++) {
  10. race++;
  11. }
  12. countDownLatch.countDown();
  13. }).start();
  14. }
  15. // 等待,直到所有线程处理结束才放行
  16. countDownLatch.await();
  17. // 期望输出 2万(2*1万)
  18. System.out.println(race);
  19. }
  20. }

熟悉的2个线程计数的例子,每个线程自增1万次,预期的结果是2万,但是实际运行结果总是一个小于等于2万的数字,为什么会这样了?
race++在我们看来可能只是1个操作,但是在底层其实是由多个操作组成的,所以在并发下会有如下的场景:
案例 - 图1
为了得到正确的结果,此时我们可以将 race++ 使用 synchronized 来修饰,如下:

  1. synchronized (SynchronizedTest.class) { race++;}

加了 synchronized 后,只有抢占到锁才能对 race 进行操作,此时的流程会变成如下:
案例 - 图2

2、synchronized 各种加锁场景?

1)作用于非静态方法,锁住的是对象实例(this),每一个对象实例有一个锁。

public synchronized void method() {}

2)作用于静态方法,锁住的是类的 Class 对象,Class 对象全局只有一份,因此静态方法锁相当于类的一个全局锁,会锁所有调用该方法的线程。

public static synchronized void method() {}

3)作用于 Lock.class,锁住的是 Lock 的 Class 对象,也是全局只有一个。

synchronized (Lock.class) {}

4)作用于 this,锁住的是对象实例,每一个对象实例有一个锁。

synchronized (this) {}

5)作用于静态成员变量,锁住的是该静态成员变量对象,由于是静态变量,因此全局只有一个。

public static Object monitor = new Object();
synchronized (monitor) {}

有些同学可能会搞混,但是其实很容易记,记住以下两点:
1)必须有“对象”来充当“锁”的角色。
2)对于同一个类来说,通常只有两种对象来充当锁:实例对象、Class 对象(一个类全局只有一份)。
Class 对象:静态相关的都是属于 Class 对象,还有一种直接指定 Lock.class。
实例对象:非静态相关的都是属于实例对象

3、为什么调用 Object 的 wait/notify/notifyAll 方法,需要加 synchronized 锁?

这个问题说难也难,说简单也简单。说简单是因为,大家应该都记得有道题目:“sleep 和 wait 的区别”,答案中非常重要的一项是:“wait会释放对象锁,sleep不会”,既然要释放锁,那必然要先获取锁。
说难是因为如果没有联想到这个题目并且没有了解的底层原理,可能就完全没头绪了。
究其原因,因为这3个方法都会操作锁对象,所以需要先获取锁对象,而加 synchronized 锁可以让我们获取到锁对象。
来看一个例子:


public class SynchronizedTest {

    private static final Object lock = new Object();

    public static void testWait() throws InterruptedException {
        lock.wait();
    }

    public static void testNotify() throws InterruptedException {
        lock.notify();
    }
}

在这个例子中,wait 会释放 lock 锁对象,notify/notifyAll 会唤醒其他正在等待获取 lock 锁对象的线程来抢占 lock 锁对象。
既然你想要操作 lock 锁对象,那必然你就得先获取 lock 锁对象。就像你想把苹果让给其他同学,那你必须先拿到苹果。
再来看一个反例:


public class SynchronizedTest {

    private static final Object lock = new Object();

    public static synchronized void getLock() throws InterruptedException {
        lock.wait();
    }
}

该方法运行后会抛出 IllegalMonitorStateException,为什么了,我们明明加了 synchronized 来获取锁对象了?
因为在 getLock 静态方法中加 synchronized 方法获取到的是 SynchronizedTest.class 的锁对象,而我们的 wait() 方法是要释放 lock 的锁对象。
这就相当于你想让给其他同学一个苹果(lock),但是你只有一个梨子(SynchronizedTest.class)。

4.锁可以降级吗

答案是可以的。
具体的触发时机:在全局安全点(safepoint)中,执行清理任务的时候会触发尝试降级锁。
当锁降级时,主要进行了以下操作:
1)恢复锁对象的 markword 对象头;
2)重置 ObjectMonitor,然后将该 ObjectMonitor 放入全局空闲列表,等待后续使用。