两个常见的并发错误

作为Baeldung的编辑,我很高兴与一位作者一起撰写有关Java通用并发陷阱的文章。 这是一本不错的书,但是假设开发人员具有一定的能力。

我已经看到了几件即时并发失败的事情。 它们很容易添加到代码中,并保证为您提供奇怪的结果。 开发人员仍会提交这些事实,这是对我们如何对OO和并发进行教育的一种评论,如果使用不当,这将非常危险。

除了代码审查

作为代码审查员,这些年来我已经开发了一些速记。 这些帮助我发现了在较大的代码更改中需要更详细地研究的区域。 它们包括红旗的事情,我希望出问题。 训练自己去发现关键的反模式或潜在的反模式是一个好主意,因为它们可以是有效的代码,但会导致无效的行为。

Bean中的请求状态

在Java应用程序中,服务,控制器,处理程序和存储库通常是单例的。 它们是在应用启动时创建的,然后请求通常通过多个线程传递给它们。

考虑如下代码:

 public void processOrder(Order order) { 
    ... 
    currentLineItem = order.getLine( 0 ); 
    processLineItem();  }  private void processLineItem() { 
    myService.store(currentLineItem);  } 

在这种情况下,该类的作者已决定该对象可以记住其当前正在处理的项目,从而节省了将该项目传递给下一个函数的工作。

这违反了两个原则:线程安全和有意义的对象状态。 订单处理者不太可能真正了解其正在处理的订单。 您可能会想像一些有状态地遍历某个订单,某种游标,阅读器或构建器的项目,但是将所有这些项目混合到一个对象中则很麻烦。

不过,最重要的是,有一个明确的定义可以解释为什么这是错误的。 如果将请求的每个属性放入该请求的接收者中,那么您将有两个风险:

  • 在多线程执行中的请求之间出血
  • 如果事情没有完全整理,则在单线程的请求之间流血

简而言之,永不做!

疯狂的懒惰初始化

延迟初始化允许:

  • 由于更快的启动
  • 必要时及时加载资源
  • 如果不需要,则不加载资源(例如,无服务器Lambda,在其生命周期中可能永远不会被要求执行特定的代码路径)
  • 定制如何通过较早发生的活动加载资源

所有这些都很好。 但是,此代码:

 private LazyService getLazyService() { 
    if (lazyService != null ) { 
       return lazyService; 
    } 
    LazyService newLazyService = connectToLazyService(); 
    registerWithServiceRegistry(newLazyService); 
    lazyService = newLazyService; 
    return newLazyService;  } 

尽管它可以工作,但可以同时调用并出错。 它的错误程度取决于各种各样的事情。 在示例中,我试图暗示我们正在处理的事情:

  • 在并发调用中,发生了多个延迟加载…
  • ……如果这很昂贵,那是浪费
  • 如果发生多个懒惰加载,则可能两个对象在内存中的驻留时间超过了所需时间,或者永远存在
  • 如果这是单例,则获取孤立对象的请求可能无法与其余请求协调
  • 使用手工进行的非线程安全的对象初始化真是遗憾

为了正确进行单例初始化,您应该使用双重检查锁定或使用框架,甚至明智地使用基于static字段的简单Java单例。

其他并发失败

以上两个似乎是最常见的错误,以至于显而易见。 如果发现另一个,请将其放在评论中。

翻译自: https://www.javacodegeeks.com/2020/01/two-common-concurrency-bugs.html

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值