同步方法无法按预期工作

时间:2015-01-05 10:09:01

标签: java multithreading

我有一个由两个线程共享的变量。这两个线程将对它进行一些操作。我不知道为什么每次执行程序时sharedVar的结果都不同。

public class Main
{
    public static int sharedVar = 0;
    public static void main(String[] args) 
    {
        MyThread mt1 = new MyThread();
        MyThread mt2 = new MyThread();
        mt1.start();
        mt2.start();

        try
        {
            // wait for the threads
            mt1.join();
            mt2.join();
        }
        catch (InterruptedException e1)
        {
            e1.printStackTrace();
        }

        System.out.println(sharedInt); // I expect this value to be 20000, but it's not
    }
}

以下是“MyThread”类

public class MyThread extends Thread
{
    private int times = 10000;
    private synchronized void addOne()
    {
        for (int i = 0; i < times; ++i)
        {
            Main.sharedVar ++;
        }
    }

    @Override
    public void run()
    {
        addOne();
    }
}

sharedVar的最终结果有时是13735,12508或18793;但从来没有20000,这是我期望的结果。该计划的另一个有趣的事情是时间= 1000。我总是得到2000作为最终结果。

任何人都可以解释这种现象吗?

5 个答案:

答案 0 :(得分:5)

同步方法保护资源this,这意味着您的代码等同于:

private void addOne()
{
    synchronized(this)
    {
        for (int i = 0; i < times; ++i)
        {
            Main.sharedVar ++;
        }
    }
}

但是您有2个对象,其中调用了addOne方法。这意味着this的{​​{1}}与mt1.addOne的{​​{1}}不同,因此您没有共同的公共资源。

尝试将this代码更改为:

mt2.addOne

您将观察到预期的行为。如下面的评论所示,最好使用与addOne不同的对象进行同步,因为类对象可以从许多点访问,而其他代码很容易尝试使用同一对象进行同步。

答案 1 :(得分:3)

在非静态方法上使用synchronized时,可以使用当前对象作为监视器。

在静态方法上使用synchronized时,使用类(ClassName.class静态字段)的当前对象作为监视器。

在您的情况下,您在Thread的对象(2个不同的实例)上使用synchronized,因此两个不同的线程将同时修改您的sharedVar静态字段。

你可以用不同的方式解决它。

addOne方法移至Main并将其设为static

private static synchronized void addOne(int times)
{
    for (int i = 0; i < times; ++i)
    {
        sharedVar++;
    }
}

或者您可以使用字段SharedVar和方法private int var;创建名为synchronized void addOne(int times)的班级,并将SharedVar的单个实例传递给您。

public static void main(String[] args) 
{
    SharedVar var = new SharedVar();
    MyThread mt1 = new MyThread(var);
    MyThread mt2 = new MyThread(var);
    mt1.start();
    mt2.start();

    try
    {
        // wait for the threads
        mt1.join();
        mt2.join();
    }
    catch (InterruptedException e1)
    {
        e1.printStackTrace();
    }

    System.out.println(var.getVar()); // I expect this value to be 20000, but it's not
}

但是如果您只需要在多个线程中更改一个整数,则可以使用java.til.concurrent.*中的类,如AtomicLongAtomicInteger

答案 2 :(得分:0)

sharedVar定义为AtomicLong而不是int。使函数synchronized也可以正常工作,但效率较低,因为您只需要同步增量。

答案 3 :(得分:0)

线程即将执行&#39; 同步&#39; 实例方法,它确定了对象的锁定(确切地说,锁定了该对象监视器)。

所以在你的情况下,线程mt1获取对象mt1上的锁定,而线程mt2获取对象mt2上的锁定,并且它们不会阻塞彼此,因为两个线程正在处理两个不同的锁。

当两个线程同时修改共享变量(非同步方式)时,结果是不可预测的。

关于值1000的情况,对于较小的输入,交错执行可能导致正确的结果(幸运的是)。

Sol:从addOne方法中删除synchronized关键字,并将sharedVal作为&#39; AtomicInteger &#39;

的类型

答案 4 :(得分:0)

启动方法后立即加入线程。从这个线程1开始并在线程2开始并进入死状态后进入死状态。因此它将始终打印您的预期输出。

更改代码,如下所示: -

public class Main{

    public static int sharedVar = 0;

    public static void main(String[] args)

        {
            MyThread mt1 = new MyThread();
            MyThread mt2 = new MyThread();

            try

                {
                    mt1.start();
                    mt1.join();
                    mt2.start();
                    mt2.join();
                }

            catch (InterruptedException e1)

                {
                    e1.printStackTrace();
                }

            System.out.println(sharedVar);

        }
}

class MyThread extends Thread
{
    private int times = 1000000;

    private synchronized void addOne()
        {
            for (int i = 0; i < times; ++i)
                {
                    Main.sharedVar++;
                }
        }

    @Override
    public void run()
        {
            addOne();
        }
}