Java - Synchronized关键字指南

Java中Synchronized关键字指南

翻译来源: Eugen Paraschiv 的 Guide to the Synchronized Keyword in Java

1.概述

这篇快速文章将介绍如何在Java中使用synchronized块。

简而言之,在多线程环境中,当两个或多个线程同时尝试更新可变共享数据时,会发生互相竞争的情况。 Java提供了一种通过同步对共享数据的线程访问来避免竞争情况的机制。

标记为synchronized的逻辑变为同步块,在任何给定时间只允许一个线程执行。

2.为什么要用 同步

让我们考虑一个典型的竞争现象,我们计算数字的总和,和多个线程执行calculate()方法:

public class BaeldungSynchronizedMethods {
 
    private int sum = 0;
 
    public void calculate() {
        setSum(getSum() + 1);
    }
 
    // standard setters and getters
}

然后让我们写一个简单的测试:

@Test
public void givenMultiThread_whenNonSyncMethod() {
    ExecutorService service = Executors.newFixedThreadPool(3);
    BaeldungSynchronizedMethods summation = new BaeldungSynchronizedMethods();
 
    IntStream.range(0, 1000)
      .forEach(count -> service.submit(summation::calculate));
    service.awaitTermination(1000, TimeUnit.MILLISECONDS);
 
    assertEquals(1000, summation.getSum());
}

我们只是使用带有3个线程的线程池的ExecutorService来执行1000次 calculate() 方法。

如果我们按顺序执行它,预期的输出将是1000,但是我们的多线程执行几乎每次都会失败,实际输出不一致,例如:

java.lang.AssertionError: expected:<1000> but was:<965>
at org.junit.Assert.fail(Assert.java:88)
at org.junit.Assert.failNotEquals(Assert.java:834)
...

这个结果当然不是意料之外的。

避免竞争条件的一种简单方法是使用synchronized关键字使操作成为线程安全的。

3. Synchronized 关键字

The synchronized keyword can be used on different levels:

  • Instance methods
  • Static methods
  • Code blocks

When we use a synchronized block, internally Java uses a monitor also known as monitor lock or intrinsic lock, to provide synchronization. These monitors are bound to an object, thus all synchronized blocks of the same object can have only one thread executing them at the same time.

3.1. Synchronized Instance Methods

Simply add the synchronized keyword in the method declaration to make the method synchronized:

public synchronized void synchronisedCalculate() {
    setSum(getSum() + 1);
}

Notice that once we synchronize the method, the test case passes, with actual output as 1000:

@Test
public void givenMultiThread_whenMethodSync() {
    ExecutorService service = Executors.newFixedThreadPool(3);
    SynchronizedMethods method = new SynchronizedMethods();
 
    IntStream.range(0, 1000)
      .forEach(count -> service.submit(method::synchronisedCalculate));
    service.awaitTermination(1000, TimeUnit.MILLISECONDS);
 
    assertEquals(1000, method.getSum());
}

Instance methods are synchronized over the instance of the class owning the method. Which means only one thread per instance of the class can execute this method.

3.2. Synchronized Static Methods

Static methods are synchronized just like instance methods:

public static synchronized void syncStaticCalculate() {
    staticSum = staticSum + 1;
}

These methods are synchronized on the Class object associated with the class and since only one Class object exists per JVM per class, only one thread can execute inside a static synchronized method per class, irrespective of the number of instances it has.

Let’s test it:

@Test
public void givenMultiThread_whenStaticSyncMethod() {
    ExecutorService service = Executors.newCachedThreadPool();
 
    IntStream.range(0, 1000)
      .forEach(count -> 
        service.submit(BaeldungSynchronizedMethods::syncStaticCalculate));
    service.awaitTermination(100, TimeUnit.MILLISECONDS);
 
    assertEquals(1000, BaeldungSynchronizedMethods.staticSum);
}

3.3. Synchronized Blocks within Methods

Sometimes we do not want to synchronize the entire method but only some instructions within it. This can be achieved by applying synchronized to a block:

public void performSynchrinisedTask() {
    synchronized (this) {
        setCount(getCount()+1);
    }
}

let us test the change:

@Test
public void givenMultiThread_whenBlockSync() {
    ExecutorService service = Executors.newFixedThreadPool(3);
    BaeldungSynchronizedBlocks synchronizedBlocks = new BaeldungSynchronizedBlocks();
 
    IntStream.range(0, 1000)
      .forEach(count -> 
        service.submit(synchronizedBlocks::performSynchronisedTask));
    service.awaitTermination(100, TimeUnit.MILLISECONDS);
 
    assertEquals(1000, synchronizedBlocks.getCount());
}

Notice, that we passed a parameter this to the synchronized block. This is the monitor object, the code inside the block get synchronized on the monitor object. Simply put, only one thread per monitor object can execute inside that block of code.

In case the method is static, we would pass class name in place of the object reference. And the class would be a monitor for synchronization of the block:

public static void performStaticSyncTask(){
    synchronized (SynchronisedBlocks.class) {
        setStaticCount(getStaticCount() + 1);
    }
}

Let’s test the block inside the static method:

@Test
public void givenMultiThread_whenStaticSyncBlock() {
    ExecutorService service = Executors.newCachedThreadPool();
 
    IntStream.range(0, 1000)
      .forEach(count -> 
        service.submit(BaeldungSynchronizedBlocks::performStaticSyncTask));
    service.awaitTermination(100, TimeUnit.MILLISECONDS);
 
    assertEquals(1000, BaeldungSynchronizedBlocks.getStaticCount());
}

4. 总结

In this quick article, we have seen different ways of using the synchronized keyword to achieve thread synchronization.

We also explored how a race condition can impact our application, and how synchronization helps us avoid that. For more about thread safety using locks in Java refer to our java.util.concurrent.Locks article.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值