同步

在 Java 中,有一个内置的语言级锁定机制:synchronized 块,它可以使用任何 Java 对象作为内部锁(即每个 Java 对象可能有一个与之关联的监视器)。

内在锁为语句组提供原子性。要了解这对我们意味着什么,让我们看看 synchronized 有用的示例:

private static int t = 0;
private static Object mutex = new Object();

public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(400); // The high thread count is for demonstration purposes.
    for (int i = 0; i < 100; i++) {
        executorService.execute(() -> {
            synchronized (mutex) {
                t++;
                System.out.println(MessageFormat.format("t: {0}", t));
            }
        });
    }
    executorService.shutdown();
}

在这种情况下,如果不是 synchronized 块,则会涉及多个并发问题。第一个是 post 增量运算符(它本身不是原子),第二个是在任意数量的其他线程有机会修改它之后我们将观察 t 的值。但是,由于我们获得了内部锁定,因此此处不存在竞争条件,输出将按正常顺序包含 1 到 100 之间的数字。

Java 中的内部锁是互斥锁 (即相互执行锁)。相互执行意味着如果一个线程已获得锁定,则第二个线程将被强制等待第一个线程释放它,然后才能为自己获取锁定。注意:可能使线程进入等待(休眠)状态的操作称为阻塞操作。因此,获取锁定是阻止操作。

Java 中的内部锁是可重入的。这意味着如果一个线程试图获取它已经拥有的锁,它将不会阻塞并且它将成功获取它。例如,以下代码在调用时不会阻塞:

public void bar(){
    synchronized(this){
        ...
    }
}
public void foo(){
    synchronized(this){
        bar();
    }
}

除了 synchronized 区块外,还有 synchronized 方法。

以下代码块实际上是等效的(即使字节码看起来不同):

  1. this 上的 synchronized 块:

    public void foo() {
        synchronized(this) {
            doStuff();
        }
    }
    
  2. synchronized 方法:

     public synchronized void foo() {
         doStuff();
     }
    

同样对于 static 方法,这个:

class MyClass {
    ...
    public static void bar() {
        synchronized(MyClass.class) {
            doSomeOtherStuff();
        }
    }
}

具有与此相同的效果:

class MyClass {
    ...
    public static synchronized void bar() {
        doSomeOtherStuff();
    }
}