Java最高

从Spring 5和Spring Boot 2开始学习的春天课程:

>>看看课程

1.概述

这篇简短的文章将介绍如何使用同步在Java中。

简单地说,在多线程环境中竞态条件当两个或多个线程试图同时更新可变共享数据时发生。Java提供了一种机制,通过同步线程访问共享数据来避免竞争条件。

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

2.为什么同步?

让我们考虑一个典型的竞争条件,在这个条件下,我们计算和,多个线程执行计算()方法:

public class 金宝搏188体育BaeldungSynchronizedMethods {private int sum = 0;public void calculate() {setSum(getSum() + 1);} //标准setter和getter方法

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

@Test public void givenMultiThread_whenNonSyncMethod() {ExecutorService服务= Executors.newFixedThreadPool(3);金宝搏188体育BaeldungSynchronizedMethods sum = new BaeldungSynchronizedMethods();IntStream。range(0, 1000) .forEach(count -> service.submit(sum::calculate));服务。awaitTermination(1000年,TimeUnit.MILLISECONDS);assertequal(1000年,summation.getSum ());}

我们只是用anExecutorService使用3线程池来执行计算()1000次。

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

在org.junit.Assert.fail(Assert.java:88) at org.junit.Assert.failNotEquals(Assert.java:834)…

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

避免竞争条件的一个简单方法是使用同步关键字。

3.的同步关键字

同步关键词可以在不同的层次上使用:

  • 实例方法
  • 静态方法
  • 代码块

当我们使用a同步块,在内部Java使用监控也称为监视器锁或内在锁,用于提供同步。这些监视器绑定到一个对象,因此同一对象的所有同步块只能有一个线程同时执行它们。

3.1.同步实例方法

简单的添加同步方法声明中的关键字以使方法同步:

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

注意,一旦我们同步了方法,测试用例就通过了,实际输出为1000:

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

实例方法同步在拥有该方法的类的实例之上。这意味着每个类的实例只有一个线程可以执行这个方法。

3.2.同步Static方法

静态方法是同步就像实例方法:

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

这些方法同步对象与类关联,因为只有一个对象存在于每个JVM每个类中,只有一个线程可以在静态同步每个类的方法,而不管它有多少个实例。

让我们测试它:

@Test public void givenMultiThread_whenStaticSyncMethod() {ExecutorService服务= Executors.newCachedThreadPool();IntStream。range(0, 1000) .forEach(count -> service.submit(金宝搏188体育BaeldungSynchronizedMethods:: syncstaticccalculate));服务。awaitTermination(100年,TimeUnit.MILLISECONDS);BaeldungSynchronize金宝搏188体育dMethods.staticSum assertequal (1000);}

3.3.同步块内的方法

有时,我们不想同步整个方法,而只想同步其中的一些指令。这可以通过应用同步到一个块:

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

让我们来测试一下这个变化:

@Test public void givenMultiThread_whenBlockSync() {ExecutorService服务= Executors.newFixedThreadPool(3);金宝搏188体育BaeldungSynchronizedBlocks = new BaeldungSynchronizedBlocks();IntStream。range(0, 1000) .forEach(count -> service.submit(synchronizedBlocks::performSynchronisedTask));服务。awaitTermination(100年,TimeUnit.MILLISECONDS);assertequal(1000年,synchronizedBlocks.getCount ());}

注意,我们传递了一个参数同步块。这是监视器对象,块中的代码在监视器对象上同步。简单地说,每个监视器对象只能在该代码块中执行一个线程。

如果方法是静态,则传递类名来代替对象引用。这个类将是块同步的监视器:

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

让我们来测试静态方法:

@Test public void givenMultiThread_whenStaticSyncBlock() {ExecutorService服务= Executors.newCachedThreadPool();IntStream。range(0, 1000) .forEach(count -> service.submit(金宝搏188体育BaeldungSynchronizedBlocks::performStaticSyncTask));服务。awaitTermination(100年,TimeUnit.MILLISECONDS);assertequal(1000年,B金宝搏188体育aeldungSynchronizedBlocks.getStaticCount ());}

3.4.可重入性

后面的锁同步方法和块是可重入的。也就是说,当前线程可以获得相同的内容同步一遍又一遍地握着它:

对象锁= new Object();synchronized (lock) {System.out。println(“首次获取”);synchronized (lock) {System.out。println(再次“进入”);synchronized (lock) {System.out。println(”和“);}}}

如上所示,当我们在同步块时,我们可以重复获取同一个监视器锁。

4.结论

在这篇简短的文章中,我们已经看到了使用the的不同方法同步关键字实现线程同步。

我们还探讨了竞争条件如何影响我们的应用程序,以及同步如何帮助我们避免这种情况。有关在Java中使金宝搏官网188be用锁的线程安全的更多信息,请参阅我们的java.util.concurrent.Locks文章

本教程的完整代码可用在GitHub

Java底部

从Spring 5和Spring Boot 2开始学习的春天课程:

>>看看课程
9评论
最古老的
最新的
内联反馈
查看所有评论
这篇文章的评论已经关闭!