为了可靠性,我将引用 Bruce Eckel 的书 - Java 哲学(第 4 版)中的引述。
我们任务的逻辑很简单:
有一个类有一个i将递增的变量和一个方法,该方法反过来会增加变量的值i。
创建类布局:
public class AtomicityTest {
private int i = 0;
public int getValue() {
return this.i;
}
public void evenIncrement() {
i++;
i++;
}
}
我们有一个变量i,将来会被多个线程访问。关于这个主题,布鲁斯·埃克尔写道:
如果多个任务同时访问一个字段,则应使用 volatile 关键字声明该字段。
好的,现在我们的类字段应该如下所示:
private volatile int i = 0;
但他在下面写道:
在 Java 虚拟机
инкремент中不是原子的......
在示例中,我们将在方法中使用атомарную自增操作++。Атомарная операция- 这是:
线程调度程序无法中断的操作 - 如果它启动,它会一直持续到完成,没有上下文切换的可能性。
我们最终会得到什么?事实证明,该操作++不是原子的,因此,声明它没有意义volatile,对吧?
阅读更多
要控制对共享资源的访问,首先将其放在对象中。之后,任何访问资源的方法都可以声明为
synchronized. 这意味着如果一个任务在一个声明为同步的方法中运行,所有其他任务将无法访问它们的synchronized方法,直到第一个任务从其调用中返回。
结果,我们完成了一个类:
public class AtomicityTest implements Runnable {
private int i = 0;
public int getValue() {
return this.i;
}
public synchronized void evenIncrement() {
i++;
i++;
}
@Override
public void run() {
while (true) {
evenIncrement();
}
}
}
由于变量的初始值为i,0并且该方法evenIncrement()将 byединице与变量相加,因此我们最终只能得到偶数。既然方法是同步的,那么:
- 线程#1 将进入方法;
- 将阻止其他线程对该方法的访问;
- 将使变量增加
2(0、2、4、6、8等) - 线程#1 将退出并解锁其他线程对该方法的访问。
好的,现在让我们开始测试:
AtomicityTest at = new AtomicityTest();
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(at);
while (true) {
int value = at.getValue();
if(value % 2 != 0) {
System.out.println("value: " + value);
System.exit(0);
}
}
然后奇迹。事实证明,在测试时,我们在输出端得到奇数。这怎么可能?问题的解决方案在于,返回一个i带有名称的值的方法getValue()应该像这样声明:
public synchronized int getValue() {
...
}
但为什么就这样呢?毕竟,一切都由上面的报价确认,并且应该在逻辑上工作,没有同步方法getValue()。
volatile例如,在这个问题中对其进行了更详细的描述。简而言之,volatile这主要是关于线程之间值的可见性,而不是关于原子性在报价中
表示如果调用了一个方法,则
synchronized不能从另一个线程调用另一个synchronized方法文档也是这样说的:
所有这一切并不与您可以
synchronized并行调用非方法的事实相矛盾,这在实践中会发生。