我正在做这个小练习,试图理解我应该如何使用并发和线程。
有时候我有一个对象无法修改其源代码并且不是线程安全的,所以我希望它只能由一个线程访问。
在此示例中,我无法触摸的第三方对象称为Holdeable。我所做的是尝试将其包装到一个名为Holder的类中,该类具有同步方法,我希望通过这样做,只有一个线程可以访问该Holdeable对象。在某些时候,我取消了对Holdeable对象的引用,我希望它正确完成,所以当另一个线程计算mHolder.getHoldeable()== null为true时,并避免输入可能导致NullPointerException的代码。
我的最后一次尝试包括一个synchronized块,即:
class Holder {
Holdeable mHoldeable;
public synchronized void setHoldeable(Holdeable holdeable) { mHoldeable = holdeable; }
public synchronized Holdeable getHoldeable() { return mHoldeable; }
}
class Holdeable { // Cannot be modified, that would be to cheat :D
public int someValue;
}
public class MainClass {
private static Holder mHolder;
public static void main(String[] args) {
try {
Holdeable holdeable = new Holdeable();
mHolder = new Holder();
mHolder.setHoldeable(holdeable);
new Thread(new Runnable() {
@Override
public void run() {
try {
while(true) {
synchronized(mHolder) {
if(mHolder.getHoldeable() != null) {
Thread.sleep(23);
System.out.println(mHolder.getHoldeable().someValue);
} else {
System.out.println("No holder!");
}
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
Thread.sleep(1000);
mHolder.getHoldeable().someValue = 2;
Thread.sleep(1500);
mHolder.getHoldeable().someValue = 3;
Thread.sleep(500);
mHolder.setHoldeable(null);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
这个例子避免了抛出NullPointerException,但正如你所看到的,执行这么多锁定需要花费很多。我在阅读“并发艺术”一书的时候正在努力解决这个问题,看看我是否最终得到它。
您怎么看?
答案 0 :(得分:2)
您的代码不是线程安全的。您有两个级别的锁定:
第1点没有给你足够的同步,因为它只涵盖了抓取 holdeable而没有访问其属性;
第2点根本没有给你任何同步,因为你只在一个线程中获取锁。
我建议你只使用第2点并一致地应用它。
BTW你的程序执行时间太长,因为它调用了Thread.sleep
。锁定的性能太高,你无法在不涉及重复循环至少数十万次的情况下注意到它。
答案 1 :(得分:0)
public class Holder {
final Holdeable mHoldeable;
Holder(Holdeable holdeable) {
this.mHoldeable = Objects.requireNonNull(holdeable, "Holdeable cannot be null");
}
Holdeable get() {
return mHoldeable;
}
}
使用这种结构将使您的生活变得更加轻松。正如您可能已经注意到的那样:它也消除了同步的需要。如果我写过一本书,那就在第一页上。 ;)
线程和同步几乎100%在架构级别上完成,添加一些同步块只是一个备份/快速&肮脏的解决方案。