头等舱:
class Main1 {
private ExecutorService service = Executors.newFixedThreadPool(4);
public static void main(String[] args) {
Main1 m = new Main1();
m.start();
}
public void start() {
final MyObject obj = new MyObject();
obj.doSomeCalculation();// after this point not to modify obj in main thread
service.submit(new Runnable(){
public void run() {
obj.doSomething(); // is it threadsafe doing this?
}
});
}
}
第二课:
class Main2 {
private ExecutorService service = Executors.newFixedThreadPool(4);
public static void main(String[] args) {
Main2 m = new Main2();
m.start();
}
public void start() {
class Job implements Runnable {
public MyObject obj;
public void run() {
obj.doSomething(); // is it threadsafe doing this?
}
}
Job job = new Job();
job.obj.doSomeCalculation(); // after this point not to modify obj in main thread
service.submit(job);
}
}
Main1
和Main2
线程是否安全? Main1和Main2对线程安全有不同的意义吗?
更新: doSomeCalulation()和doSomething()都没有任何锁定或同步块。我想知道doSomething()是否总能读取doSomeCalculation()更改为obj的正确状态
答案 0 :(得分:3)
Main1,Main2线程是否安全?
在Main1
的情况下,应用程序的线程安全性取决于MyObject
是否是线程安全的,以及是否有任何其他线程使用它。但是,obj.doSomething();
语句是线程安全的,假设没有其他任何东西在改变对象
实际上,obj.doSomething();
语句不使用封闭类中的变量。相反,该变量的值将传递给隐藏构造函数参数中的内部类。使线程安全的另一个原因是在创建新线程时父线程和子线程之间存在隐式同步。 (参考 - JLS 17.4.4 Synchronization Order)这两个事实相结合意味着Runnable.run()
方法将获得正确的引用,并且子线程将在同步点(或更高版本)看到对象的状态。
在Main2
案例中,同样适用。在这种情况下,您只是明确地(或多或少)明确地在Main1
案例中发生了什么。
UPDATE - 即使您在将父对象中的对象(根据您更新的问题)变更之前,上述推理也适用,然后再将其传递给子线程...因为我隐式同步提及。 (但是,如果父线程在MyObject
调用后要更改submit()
,则会遇到线程安全问题。)
Main1和Main2有不同的含义吗?
我不知道你在问什么。如果你在询问使用内部类而不是匿名内部类是否有任何好处...在这种情况下答案是否定的。它们在线程安全方面表现相同。
实际上,Main1
版本更好,因为它更简单,更易读(对于有经验的Java开发人员),并且更加健壮。 Main2
类公开了obj
字段,以便其他代码可能访问甚至更新。这是糟糕的风格。你可以解决这个问题,但只能添加更多代码......这将我们带回简单/可读性。
答案 1 :(得分:1)
您的代码的结构方式,只有在您完成上述计算后才能提交您的工作(在这两种情况下)。因此,这两个动作不可能并行发生,因此没有数据竞赛。
但是,如果您在将作业/ Runnable提交给执行程序服务之后执行计算,则这两个计算可能会并行发生,并且可能存在数据争用。
Job job = new Job();
service.submit(job);
// Now there is a data race!!!
job.obj = ...// do some calculation, and after this point not to modify obj in main thread