有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

使用静态属性vs共享属性vs同步方法的java同步块

我测试了同一问题的三个版本,希望所有版本都能提供完美的同步:

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

只有第二个返回预期结果。为什么其他两个都失败了


共 (3) 个答案

  1. # 1 楼答案

    只有第二种方法在所有线程中使用相同的同步对象。因此,只有第二种方法才能达到预期的效果

    在第一种方法中,每个线程都会生成自己的new Object()。例如,必须使用一个中心对象SynchronizedIncrement.classstatic int x(可能重构为Integer以获得一个对象,而不是原语)

    我对你的第三种方法的结果还有些不清楚。这可能是由于在所有线程终止之前打印结果造成的

  2. # 2 楼答案

    1. 第一个代码将使用引用o作为同步的对象监视器。对象o对于SynchronizedIncrement的每个实例都是不同的对象,因此每个线程将锁定自己的监视器,允许它们并行运行,从而不一致地增加静态变量x

    2. 第二个实现将使用作为参数传递给构造函数的对象来锁定线程。你有一个单一的引用(obj),你正在传递给所有的线程,在这种情况下,所有的同步都将在同一个对象上完成,因此静态变量x的增量是一致的

    3. 最后一段代码将在“this”(自身)上同步,因此其行为与第一个版本相同

    通常的设计是将多线程访问的数据封装在不同的类中,并将接口方法与该类同步

    在这里,一个折衷方案是使用incX()方法并声明:

    static synchronized void incX(){
        x++;
    }
    

    在这种情况下,不需要同步运行方法

  3. # 3 楼答案

    在第一个和第三个示例中,所有线程都使用另一个对象进行同步

    第一个例子:

    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;
    }
    

    只有第二个示例是正确同步的,因为所有线程都使用相同的对象进行同步