同步方法没有按预期工作
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,比如AtomicLong
或AtomicInteger
。
同步方法保护资源 this
这意味着您的代码等同于:
private void addOne()
{
synchronized(this)
{
for (int i = 0; i < times; ++i)
{
Main.sharedVar ++;
}
}
}
但是您有 2 个对象调用了 addOne
方法。这意味着 mt1.addOne
的 this
与 mt2.addOne
的 this
不同,因此您没有共同的同步资源。
尝试将您的 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();
}
}
我有一个由两个线程共享的变量。两个线程会对它做一些操作。不知道为什么每次执行程序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,比如AtomicLong
或AtomicInteger
。
同步方法保护资源 this
这意味着您的代码等同于:
private void addOne()
{
synchronized(this)
{
for (int i = 0; i < times; ++i)
{
Main.sharedVar ++;
}
}
}
但是您有 2 个对象调用了 addOne
方法。这意味着 mt1.addOne
的 this
与 mt2.addOne
的 this
不同,因此您没有共同的同步资源。
尝试将您的 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();
}
}