方法何时应声明为同步?

时间:2018-12-19 16:15:36

标签: java multithreading concurrency thread-synchronization

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

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

3 个答案:

答案 0 :(得分:5)

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

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)中等待。

答案 1 :(得分:2)

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

如果要使类线程安全,从而使客户端不需要执行任何特定操作即可使用它,则它应具有适当的同步机制,并提出可以在组合操作上运行原子操作的“高级”方法如有必要。

例如,考虑AtomicInteger,它具有incrementAndGet()方法,该方法将提供更好的方法。您的run方法将变为:

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

答案 2 :(得分:1)

您的increment()方法使用的i++实际上是由3条指令代替的。如果未将该方法声明为synchronized,则多个线程可能会同时执行该方法,这将导致争用条件和错误结果。

  

如果增量()和getValue()被声明为已同步,并且   注释了同步的块代码,输出显示可见性   和种族问题。

在这种情况下,没有比赛条件。您会看到不同的结果,因为现在increment()getValue()并不是原子执行的(原子上我是指两个方法作为一个同步块),而一个线程名为increment()并即将调用{{ 1}}的另一个线程也称为getValue()

因此,如果您打算在increment()方法的increment()块之外调用getValue()synchronized(并且因为它们被声明为run可以拨打电话。