什么时候应该将方法声明为同步的?

When should a method be declared synchronized?

以下是一个简单的工作程序,它使用两个线程打印一个计数器:

public class SynchronizedCounter implements Runnable {

    private static int i = 0;

    public void increment() { i++; }
    public int getValue() { return i; }  

    @Override
    public void run() {
        for( int i = 0; i < 5; i++ ) {
            synchronized( this ) {
                increment();
                System.out.println( Thread.currentThread().getName() + " counter: " + this.getValue() );
            }
        }
    }

    public static void main( String[] args ) {

        ExecutorService executorService = Executors.newFixedThreadPool( 2 );
        SynchronizedCounter synchronizedCounter = new SynchronizedCounter();
        executorService.submit( synchronizedCounter );
        executorService.submit( synchronizedCounter );  
        executorService.shutdown();

    }

}

输出符合预期 - 两个线程按顺序显示计数器。

如果 increment()getValue() 被声明为 synchronized 并且 synchronized 块代码被注释,输出表明可见性和竞争问题。

因为 increment()getValue() 没有被声明为 synchronized 并且如果可以实现同步,只需使用 synchronized 块(传递监视器对象) , 在什么情况下应该声明为 synchronized?

当您需要方法主体周围的同步块的语义时,声明一个同步方法。

synchronized void increment() {
  ...
}

完全相同:

void increment() {
  synchronized (this) { ... }
}

在上面的代码中这样做的事情是你不再自动执行 increment()getValue() 方法:另一个线程可以插入并在你的调用之间执行这些方法线程:

Thread 1             Thread 2

increment()
                     increment()
                     getValue()
getValue()

这是可以的,但是两个线程不能同时执行increment()(或getValue()),因为它们是同步的(*)。

另一方面,在问题的代码中围绕调用进行同步意味着这两个调用是原子执行的,因此两个线程不能交错:

Thread 1             Thread 2

increment()
getValue()
                     increment()
                     getValue()

(*) 实际上,它们可以同时执行该方法。只是除了一个线程之外的所有线程都将在 synchronized (this).

处等待

在您的代码中,class 本身不是线程安全的,由客户端(您的 run 方法)来确保线程安全。

如果你想让 class 线程安全,这样客户端不需要做任何特别的事情来使用它,它应该有适当的同步机制并提出 "higher level" 方法可以 运行 必要时对操作组合进行原子操作。

例如,想想 AtomicInteger,它有一个 incrementAndGet() 方法,可以提供更好的方法。您的 run 方法将变为:

for( int i = 0; i < 5; i++ ) {
  int newValue = counter.incrementAndGet();
  System.out.println(Thread.currentThread().getName() + " counter: " + newValue );
}

您的 increment() 方法使用了 i++,它实际上被 3 条指令所取代。如果该方法未声明为 synchronized,则多个线程可能同时执行它,这将导致竞争条件和不正确的结果。

If increment() and getValue() are declared as synchronized and synchronized block code is commented, the output manifests visibility and race problems.

在这种情况下没有竞争条件。您会看到不同的结果,因为现在 increment()getValue() 不是原子执行的(原子地我的意思是两种方法作为一个同步块),而一个线程调用 increment() 并且即将调用 getValue() 另一个线程也称为 increment()

因此,如果您打算在 run 方法的 synchronized 块之外调用它们,则需要使 increment()getValue() 同步(因为它们是声明为 public 可以进行此类调用)。