简体   繁体   English

同步方法无法按预期工作

[英]Synchronized method does not work as expected

I have a variable which is shared by two threads. 我有一个由两个线程共享的变量。 The two threads will do some operations on it. 这两个线程将对它进行一些操作。 I don't know why the result of sharedVar is different every time I execute the program. 我不知道为什么每次执行程序时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
    }
}

The following is the class "MyThread" 以下是“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();
    }
}

The final result of sharedVar sometimes are 13735, 12508, or 18793; sharedVar的最终结果有时是13735,12508或18793; but never 20000, which is the result I expect. 但从来没有20000,这是我期望的结果。 Another interesting thing about the program is when times=1000. 该计划的另一个有趣的事情是时间= 1000。 I always get 2000 as the final result. 我总是得到2000作为最终结果。

Can anyone explain this phenomenon? 谁能解释这个现象?

A synchronized method protects the resource this that means that your code is equivalent to: synchronized方法保护资源, this意味着您的代码等效于:

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

But you have 2 objects for which addOne method is called. 但是您有2个对象,其中addOne方法。 That means this for mt1.addOne is not the same than this for mt2.addOne and therefore you don't have a common resource of synchronization. 这意味着thismt1.addOne并不比同thismt2.addOne ,因此您不必同步的公共资源。

Try changing yout addOne code to: 尝试将addOne代码更改为:

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

And you will observe the expected behaviour. 你会观察到预期的行为。 As the comments below suggest, it is better to use a different object than MyThread.class for synchronization since class objects are accesible from many points and it is easy that other code may try to synchronize using the same object. 正如下面的注释所示,最好使用与MyThread.class不同的对象进行同步,因为类对象可以从许多点访问,而其他代码很容易尝试使用同一个对象进行同步。

When you use synchronized on non-static method, you use current object as monitor. 在非静态方法上使用synchronized时,使用当前对象作为监视器。

When you use synchronized on static method, you use current object of class ( ClassName.class static field) as monitor. 当您使用synchronized on static方法时,您使用类的当前对象( ClassName.class静态字段)作为监视器。

In your case, you use synchronized on Thread's object (2 different instances), so two different threads will modify your sharedVar static field at same time. 在您的情况下,您在Thread的对象(2个不同的实例)上使用synchronized ,因此两个不同的线程将同时修改您的sharedVar静态字段。

You can fix it in different ways. 您可以通过不同方式修复它。

Move addOne method to Main and make it static . addOne方法移动到Main并使其成为static

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

Or you can create class called SharedVar with field private int var; 或者您可以使用字段private int var;创建名为SharedVarprivate int var; and method synchronized void addOne(int times) and pass single instance of SharedVar to your treads. 和方法synchronized void addOne(int times)并将SharedVar单个实例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
}

But if you need only one integer to be changed in multiple threads, you can use classes from java.til.concurrent.* , like AtomicLong or AtomicInteger . 但是如果只需要在多个线程中更改一个整数,则可以使用java.til.concurrent.*类,如AtomicLongAtomicInteger

Define sharedVar as an AtomicLong instead of int . sharedVar定义为AtomicLong而不是int Making the function synchronized works as well but it is less efficient because you only need the increment to be synchronized. 使函数synchronized起作用,但效率较低,因为您只需要同步增量。

When a thread is about to execute a ' synchronized ' instance method , it aqcuires the lock on the Object(to be precise, lock on that object monitor). 当线程即将执行' synchronized ' 实例方法时 ,它会对Object上的锁定(确切地说,锁定该对象监视器)。

So in your case, Thread mt1 acquires lock on Object mt1 and Thread mt2 acquires lock on Object mt2 and they do not block each Other as the two threads are working on two different locks. 所以在你的情况下,线程mt1获取对象mt1上的锁定,并且线程mt2获取对象mt2的锁定,并且它们不会阻塞彼此,因为两个线程正在处理两个不同的锁定。

And when two threads modify a shared variable concurrently(not synchronized way), the result is unpredictable. 当两个线程同时修改共享变量(非同步方式)时,结果是不可预测的。

Well about the case of value 1000, for smaller inputs the interleaved execution might have resulted in correct result(luckily). 关于值1000的情况,对于较小的输入,交错执行可能导致正确的结果(幸运的是)。

Sol : remove the synchronized keyword from addOne method and make sharedVal as type of ' AtomicInteger ' Sol:从addOne方法中删除synchronized关键字,并将sharedVal作为' AtomicInteger '的类型

Join the thread immediately after start method. 启动方法后立即加入线程。 From this thread-1 will start and go to dead state after that thread-2 will start and go to dead state. 从这个线程1开始并在线程2开始并进入死状态后进入死状态。 So it will print your expected output always. 因此它将始终打印您的预期输出。

Change the code as shown below:- 更改代码如下所示: -

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM