同步方法没有按预期工作

Synchronized method does not work as expected

我有一个由两个线程共享的变量。两个线程会对它做一些操作。不知道为什么每次执行程序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
    }
}

以下是class"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,这是我期望的结果。该程序的另一个有趣之处在于 times=1000。我总是得到 2000 作为最终结果。

谁能解释一下这个现象?

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

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

在你的例子中,你在 Thread 的对象上使用了 synchronized(2 个不同的实例),所以两个不同的线程将同时修改你的 sharedVar 静态字段。

您可以通过不同的方式修复它。

addOne 方法移动到 Main 并使其成为 static.

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

或者您可以使用字段 private int var; 和方法 synchronized void addOne(int times) 创建名为 SharedVar 的 class,并将 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.*中的classes,比如AtomicLongAtomicInteger

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

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

但是您有 2 个对象调用了 addOne 方法。这意味着 mt1.addOnethismt2.addOnethis 不同,因此您没有共同的同步资源。

尝试将您的 addOne 代码更改为:

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

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

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

当一个线程即将执行'synchronized'实例方法时,它会获取对象上的锁(准确地说,锁定那个对象监视器)。

因此,在您的情况下,线程 mt1 获取对象 mt1 的锁,线程 mt2 获取对象 mt2 的锁,并且它们不会相互阻塞,因为这两个线程正在处理两个不同的锁。

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

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

Sol : 从 addOne 方法中删除 synchronized 关键字并将 sharedVal 设为 'AtomicInteger'

的类型

在启动方法后立即加入线程。从这个线程 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();
        }
}