Java 什么时候需要上锁

Java 什么时候需要上锁,java,multithreading,concurrency,consistency,Java,Multithreading,Concurrency,Consistency,好吧,我知道这听起来很愚蠢(恐怕是的),但我对自己给出的答案并不完全满意,所以我认为在这里问这个问题是值得的。 我正在处理一个关于并发性的练习(在Java中),它是这样的 给定已解决的数独图表,使用同时运行的固定数量的线程确定图表是否已正确解决,即没有违反规范规则的情况发生(数字必须在其行、列和块中仅出现一次) 现在我的问题是:既然线程只需要执行“读取”,从图表中收集信息并在其他地方进行详细说明,那么它们就不能在不担心并发性的情况下工作吗?图表的状态总是一致的,因为没有执行“写入”,所以它永远不

好吧,我知道这听起来很愚蠢(恐怕是的),但我对自己给出的答案并不完全满意,所以我认为在这里问这个问题是值得的。 我正在处理一个关于并发性的练习(在Java中),它是这样的

给定已解决的数独图表,使用同时运行的固定数量的线程确定图表是否已正确解决,即没有违反规范规则的情况发生(数字必须在其行、列和块中仅出现一次)

现在我的问题是:既然线程只需要执行“读取”,从图表中收集信息并在其他地方进行详细说明,那么它们就不能在不担心并发性的情况下工作吗?图表的状态总是一致的,因为没有执行“写入”,所以它永远不会更改


当且仅当存在资源一致性丢失风险时,是否需要锁/同步块/同步方法?换句话说,我对并发的理解正确吗?

在我看来,您的理解是正确的。只有当任何线程正在写入数据时,才会发生数据损坏

如果您100%确定没有线程正在写入,那么跳过同步和锁定是安全的

编辑:在这些情况下跳过锁定是最佳实践!
:)

如果文件是只读的,则无需同步该文件。基本上,锁应用于关键部分。关键部分是,不同线程同时访问共享内存。
由于同步会使程序变慢,因为没有多个线程同时访问,所以最好不要在只读文件的情况下使用锁。

从我的观点来看,如果您写入,则需要锁定,并且由于网络延迟或大量处理开销,写入需要很长时间才能完成。
否则,关闭锁定是非常安全的。

想象一下您有一大堆工作要完成(检查9行、9列、9块)。如果您希望线程完成这组27个工作单元,并且如果您希望在没有双重工作的情况下完成工作,则需要同步线程。另一方面,如果您乐于让线程执行由另一个线程完成的工作单元,则不需要同步线程。

这是一个相当微妙的问题,一点也不愚蠢

仅当数据结构已安全发布时,同时读取数据结构的多个线程才可以在不同步的情况下执行此操作。这是内存可见性问题,不是计时问题或争用条件

有关安全发布概念的进一步讨论,请参见Goetz等人的《Java并发实践》第3.5节。第3.5.4节“有效不可变对象”似乎适用于此,因为电路板在某一点上变得有效不可变,因为它在达到已解决状态后从未写入

简而言之,编写器线程和读取器线程必须执行一些内存协调活动,以确保读取器线程对所写内容具有一致的视图。例如,writer线程可以写入数独板,然后在持有锁的同时,将对该板的引用存储在静态字段中。然后,读取线程可以在保持锁的同时加载该引用。一旦他们这样做了,他们就可以确保之前对电路板的所有写入都是可见的和一致的。之后,读卡器线程可以自由访问电路板结构,无需进一步同步

还有其他方法来协调内存可见性,例如写入/读取易失性变量或
AtomicReference
。使用更高级别的并发构造(如闩锁或屏障),或将任务提交给
执行器服务
),也将提供内存可见性保证

更新


根据与的评论中的交流,我还应该指出,当从读者线程返回结果时,安全发布要求也适用。也就是说,一旦其中一个读线程从其计算部分获得结果,它就需要将该结果发布到某个地方,以便与其他读线程的结果组合。可以像以前一样使用相同的技术,例如共享数据结构上的锁定/同步、volatiles等。但是,这通常是不必要的,因为结果可以从
ExecutorService.submit
invoke
返回的
未来
中获得。这些构造自动处理安全发布要求,因此应用程序不必处理同步。

Thread1
写入一些数据,然后一组线程需要读取这些数据的情况下,如果操作正确,则不需要锁定。正确地说,我的意思是你的数独板是一个不可变的对象,不可变的对象是指:

  • 构造后无法修改状态
  • 状态实际上并不是通过反射黑魔法来改变的
  • 所有字段都是最终字段
  • “this”引用在构造过程中不会消失(如果在构造过程中您按照
    MyClass.instnce=this
    的思路执行某些操作,则可能会发生这种情况)
如果将此对象传递给工作线程,则可以继续。如果您的对象不满足所有这些条件,您仍然可能会遇到并发问题,在大多数情况下,这是因为JVM可能会随意对语句重新排序(出于性能原因),并且它可能会以这样的方式对这些语句重新排序,即在构建数独板之前启动工作线程


这是一篇非常好的文章

摘要

为了保证线程能够观察到写入主存的效果,写入必须在读取之前发生。如果写入和读取发生在不同的线程中,