java线程安全是什么意思_Java中的线程安全性是什么意思?

“线程安全”实际上意味着什么?

通过优锐课的学习分享,讨论了关于Java中的线程安全性意味着类的方法是原子的或静态的。 那么原子是什么,静止是什么意思呢? 为什么在Java中没有其他类型的线程安全方法?

“原子”是什么意思?

当方法调用似乎立即生效时,该方法就是原子的。 因此,其他线程在方法调用之前或之后只能看到状态,而没有中间状态。 让我们看一下非原子方法,看看原子方法如何使类具有线程安全性。

public class UniqueIdNotAtomic {

private volatile long counter = 0;

public long nextId() {

return counter++;

}

}

类UniqueIdNotAtomic通过使用易失性变量计数器创建唯一的ID。 我在第2行使用了volatile字段,以确保线程始终看到当前值,如此处更详细的说明。 要查看此类是否是线程安全的,我们使用以下测试:

public class TestUniqueIdNotAtomic {

private final UniqueIdNotAtomic uniqueId = new UniqueIdNotAtomic();

private long firstId;

private long secondId;

private void updateFirstId() {

firstId = uniqueId.nextId();

}

private void updateSecondId() {

secondId = uniqueId.nextId();

}

@Test

public void testUniqueId() throws InterruptedException {

try (AllInterleavings allInterleavings =

new AllInterleavings("TestUniqueIdNotAtomic");) {

while(allInterleavings.hasNext()) {

Thread first = new Thread( () -> { updateFirstId(); } ) ;

Thread second = new Thread( () -> { updateSecondId(); } ) ;

first.start();

second.start();

first.join();

second.join();

assertTrue( firstId != secondId );

}

}

}

}

为了测试计数器是否是线程安全的,我们需要在第16和17行中创建两个线程。我们启动这两个线程(第18和19行)。然后,我们等待直到两个线程都通过第20和21行结束。 在两个线程都停止之后,我们检查两个ID是否唯一,如第22行所示。

为了测试所有线程交织,我们使用来自vmlens第15行的AllInterleavings类,将完整的测试放在while循环中迭代所有线程交织。

运行测试,我们看到以下错误:

java.lang.AssertionError:

at org.junit.Assert.fail(Assert.java:91)

at org.junit.Assert.assertTrue(Assert.java:43)

发生该错误的原因是,由于操作++不是原子操作,因此两个线程可以覆盖另一个线程的结果。 我们可以在vmlens的报告中看到这一点:

在发生错误的情况下,两个线程首先并行读取变量计数器。 然后,两个都创建相同的ID。 为了解决这个问题,我们通过使用同步块使方法原子化:

private final Object LOCK = new Object();

public long nextId() {

synchronized(LOCK) {

return counter++;

}

}

现在,该方法是原子的。 同步块可确保其他线程无法看到该方法的中间状态。

不访问共享状态的方法是自动原子的。 具有只读状态的类也是如此。 因此,无状态和不可变的类是实现线程安全类的简便方法。 他们所有的方法都是自动的。

并非原子方法的所有用法都是自动线程安全的。 将多个原子方法组合为相同的值通常会导致争用条件。 让我们看看从ConcurrentHashMap获取和放置的原子方法以了解原因。 当以前的映射不存在时,让我们使用这些方法在映射中插入一个值:

public class TestUpdateTwoAtomicMethods {

public void update(ConcurrentHashMap map) {

Integer result = map.get(1);

if( result == null ) {

map.put(1, 1);

}

else {

map.put(1, result + 1 );

}

}

@Test

public void testUpdate() throws InterruptedException {

try (AllInterleavings allInterleavings =

new AllInterleavings("TestUpdateTwoAtomicMethods");) {

while(allInterleavings.hasNext()) {

final ConcurrentHashMap map =

new ConcurrentHashMap();

Thread first = new Thread( () -> { update(map); } ) ;

Thread second = new Thread( () -> { update(map); } ) ;

first.start();

second.start();

first.join();

second.join();

assertEquals( 2 , map.get(1).intValue() );

}

}

}

}

该测试与先前的测试相似。 再次,我们使用两个线程来测试我们的方法是否是线程安全的(第18行和第19行)。再次,我们在两个线程完成之后测试结果是否正确(第24行)。运行测试,我们看到以下错误:

java.lang.AssertionError: expected:<2> but was:<1>

at org.junit.Assert.fail(Assert.java:91)

at org.junit.Assert.failNotEquals(Assert.java:645)

该错误的原因是,两种原子方法get和put的组合不是原子的。 因此,两个线程可以覆盖另一个线程的结果。 我们可以在vmlens的报告中看到这一点:

在发生错误的情况下,两个线程首先并行获取值。 然后,两个都创建相同的值并将其放入地图中。 要解决这种竞争状况,我们需要使用一种方法而不是两种方法。 在我们的例子中,我们可以使用单个方法而不是两个方法get和put来进行计算:

public void update() {

map.compute(1, (key, value) -> {

if (value == null) {

return 1;

}

return value + 1;

});

}

因为方法计算是原子的,所以这解决了竞争条件。 虽然对ConcurrentHashMap的相同元素进行的所有操作都是原子操作,但对整个地图(如大小)进行操作的操作都是静态的。 因此,让我们看看静态意味着什么。

“静止”是什么意思?

静态意味着当我们调用静态方法时,我们需要确保当前没有其他方法在运行。 下面的示例显示如何使用ConcurrentHashMap的静态方法大小:

ConcurrentHashMap map =

new ConcurrentHashMap();

Thread first = new Thread(() -> { map.put(1,1);});

Thread second = new Thread(() -> { map.put(2,2);});

first.start();

second.start();

first.join();

second.join();

assertEquals( 2 , map.size());

通过等待直到所有线程都使用线程连接完成为止,当我们调用方法大小时,我们确保没有其他线程正在访问ConcurrentHashMap。

方法大小使用在java.util.concurrent.atomic.LongAdder,LongAccumulator,DoubleAdder和DoubleAccumulator类中也使用的一种机制来避免争用。 与其使用单个变量存储当前大小,不如使用数组。 不同的线程更新数组的不同部分,从而避免争用。 该算法在Striped64的Java文档中有更详细的说明。

静态类和静态方法对于收集竞争激烈的统计数据很有用。 收集数据后,可以使用一个线程来评估收集的统计信息。

为什么在Java中没有其他线程安全方法?

在理论计算机科学中,线程安全性意味着数据结构满足正确性标准。 最常用的正确性标准是可线性化的,这意味着数据结构的方法是原子的。

对于常见的数据结构,存在可证明的线性化并发数据结构,请参见Maurice Herlihy和Nir Shavit撰写的《多处理器编程的艺术》一书。 但是要使数据结构线性化,需要使用比较和交换之类的昂贵同步机制,请参阅论文《定律:无法消除并发算法中的昂贵同步》以了解更多信息。

因此,研究了其他正确性标准(例如静态)。 因此,我认为问题不在于“为什么Java中没有其他类型的线程安全方法?” 但是,Java何时将提供其他类型的线程安全性?

结论

Java中的线程安全性意味着类的方法是原子的或静态的。 当方法调用似乎立即生效时,该方法就是原子的。 静态意味着当我们调用静态方法时,我们需要确保当前没有其他方法在运行。

目前,静态方法仅用于收集统计信息,例如ConcurrentHashMap的大小。 对于所有其他用例,使用原子方法。 让我们拭目以待,未来是否会带来更多类型的线程安全方法。

文章写道这里,如有不足之处,欢迎补充评论。

如果你对java技术很感兴趣也可以一起交流学习,共同学习进步!

2496cb43573163e906a5393dd99f4179.png

最近get了很多新知识,希望能帮到大家。需要详细的java架构思维导图路线也可以评论获取!

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值