我测试了同一问题的三个版本,期望所有人提供完美的同步:
1)使用静态变量作为锁:
public class SynchronizedIncrement implements Runnable {
private static int x = 0;
private Object o = new Object();
public void run() {
for (int i=0; i<10000; ++i)
synchronized(o) {
++x;
}
}
}
2)使用与lock相同的对象,作为构造函数中的参数传递:
public class SynchronizedIncrement implements Runnable {
private static int x = 0;
private Object o = null;
public SynchronizedIncrement(Object o) {
this.o = o;
}
public void run() {
for (int i=0; i<10000; ++i)
synchronized(o) {
++x;
}
}
}
3)将run()
声明为synchronized
方法:
public synchronized void run() {
for (int i=0; i<10000; ++i)
++x;
}
我正在使用100个线程的固定线程池进行测试:
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(100);
//Object obj = new Object(); // used as argument for the second version
for (int i=0; i<100; ++i)
es.submit(new Thread(new SynchronizedIncrement()));
es.shutdown();
while (!es.isTerminated());
System.out.println(x);
}
版本1,2和3的输出:
560126
1000000
976082
只有第二个返回预期结果。为什么其他两个失败?
答案 0 :(得分:3)
第一个代码将使用引用o作为对象监视器进行同步。对象o是SynchronizedIncrement的每个实例的不同对象,因此每个线程将锁定其自己的监视器,允许它们全部并行运行,从而不一致地增加静态变量x。
第二个实现将使用作为参数传递给构造函数的对象来锁定线程。您有一个单一的引用(obj),您将传递给所有线程,在这种情况下,所有同步都将在同一个对象上完成,从而一致地增加静态变量x。
最后一段代码将同步于&#34;这个&#34; (本身)所以它的行为与第一个版本相同。
通常的设计是将多个线程访问的数据封装在不同的类中,并将接口方法与该类同步。
在这里,权衡将是一个方法incX()并声明:
static synchronized void incX(){
x++;
}
在这种情况下,您的运行方法不需要同步。
答案 1 :(得分:2)
在第一个和第三个示例中,所有线程都使用另一个对象进行同步。
第一个例子:
public class SynchronizedIncrement implements Runnable {
private static int x = 0;
private Object o = new Object(); // Each new SynchronizedIncrement will create its own new Object.
public void run() {
for (int i=0; i<10000; ++i)
synchronized(o) { // All threads can still interleave and access 'x'.
++x;
}
}
}
第三个例子:
public synchronized void run() { // Now the threads synchronize again on its own object each: the thread itself!
for (int i=0; i<10000; ++i)
++x;
}
只有第二个示例才能正确同步,因为所有线程都使用相同的对象进行同步。
答案 2 :(得分:0)
只有第二种方法在所有线程中使用相同的同步对象。因此,只有第二个提供了预期的结果。
在第一种方法中,每个线程都会生成自己的new Object()
。您必须使用一个中心对象 - 例如SynchronizedIncrement.class
或static int x
(可能重构为Integer
以获取对象而不是原始对象。
你的第三种方法的结果对我来说仍然有点不清楚。这可能是由于在所有线程终止之前打印结果造成的。