这是关于Java中多线程的基本问题:我有一个非常大的可变数据结构(确切地说是一棵树)我明白如果我想从两个不同的线程同时修改这个数据结构,我需要使用锁和/或其他类型的线程安全。
但是,在我的情况下,两个线程不需要同时修改数据结构;相反,通常拥有数据结构的线程A应该暂时将后者传递给线程B,并且线程B应该在对它进行一些长时间运行的修改后将数据结构传递回线程A.
如果保证线程不会同时修改数据,那么在线程之间来回传递这个可变数据结构是否是线程安全的吗?
答案 0 :(得分:12)
如果你能保证线程不会同时修改树(即通过原子方式通过对树的唯一引用),从线程安全的角度来看就没问题。
但数据可见性/一致性是另一个问题。除非树中的所有字段都是(递归地)声明volatile
,否则一个线程所做的更改可能不会对另一个线程可见。为避免这种情况,请确保在线程交换树的所有权时获取监视器(充当内存屏障并确保所有写入变为可见)。
答案 1 :(得分:9)
是的,当您在线程之间传递对象时,只要您采取特定步骤以避免内存一致性错误,您所描述的内容就可以正常工作。使用锁定是实现这一目标的一种方法,但还有其他方法 - 更便宜 - 。
tutorial是一个很好的起点。
基本上,您需要确保当线程A将对象传递给线程B时,A 的所有更改都会在 B访问对象之前发生。
JLS还有更多内容,但它相当技术性。
答案 2 :(得分:4)
如果没有进一步的同步保证,这是不线程安全。
基本上,如果没有正确的同步(例如synchronized
或其他事先发生的JLS保证),就无法确定线程之间的一致可见性。也就是说,即使可能没有"并发修改",也有 no 保证非写入者线程看到对所述对象的修改。
答案 3 :(得分:4)
是的,但这仍然引发了如何在线程之间转移数据所有权的问题。答案是这通常是用锁来完成的;持有锁表示给定的线程当前是允许改变对象的负责人。
当有一个或多个读者和至少一个作者时,注意线程安全是一个问题也是很重要的。仅仅因为两个对象不是同时修改对象,如果一个线程在被突变的同时从结构中读取,它仍然是个问题。
所以,要明白这一点:保持简单,只需使用锁。
答案 4 :(得分:2)
一般来说,它不是,但在许多特定情况下,它可能会发挥作用(只是不依赖于那些)。
锁定会做两件事。
synchronized
。如果以某种方式你可以建立一个自己锁定的机制(这就是你所说的),你仍然需要确保所有数据在线程之间同步。锁定不是唯一的方法,但它是它的一个附加功能。
答案 5 :(得分:2)
我假设您已经创建了自定义数据结构。
尝试创建dataStructure类Synchronized
like this
如果需要,请谨慎使用synchronized
块。
如果你需要在所有线程上共享相同的数据,那么创建static Synchronized
方法和private static
变量,那么即使你错误地创建了dataStructure类的多个实例,它也是线程安全的
答案 6 :(得分:0)
这可以通过一个简单的volatile辅助变量来完成:
class OwnedTree{
private volatile Thread owner;
private Tree tree;
//once a thread calls this with a the!=Thread.currentThread() then it may not use Tree returned from getTree any more
public void passToThread(Thread thr){
if(Thread.currentThread().equals(owner))
owner = thr;//volatile write ensures happens-before
}
public Tree getTree(){
if(!Thread.currentThread().equals(owner))//volatile read ensures happens-before
throw new IllegalStateException();//or return null;
return tree;
}
public Thread getOwner(){
return owner;
}
}
易失性读写的语义确保如果正确使用此类,所有更改都将可见