我读过有关Java中原子操作的文章,但仍有一些疑问需要澄清:
int volatile num;
public void doSomething() {
num = 10; // write operation
System.out.println(num) // read
num = 20; // write
System.out.println(num); // read
}
所以我在1方法上完成了w-r-w-r 4操作,它们是原子操作吗?如果多个线程同时调用doSomething()方法会发生什么?
答案 0 :(得分:6)
如果没有线程会看到中间状态,那么操作就是原子操作,即操作将完全完成,或者根本不完成。
读取int字段是原子操作,即一次读取所有32位。编写一个int字段也是原子的,该字段要么已完全写入,要么根本不写。
然而,方法doSomething()不是原子的;在执行该方法时,一个线程可能会将CPU输出到另一个线程,并且该线程可能会看到已执行了一些但不是所有操作。
也就是说,如果线程T1和T2都执行doSomething(),则可能发生以下情况:
T1: num = 10;
T2: num = 10;
T1: System.out.println(num); // prints 10
T1: num = 20;
T1: System.out.println(num); // prints 20
T2: System.out.println(num); // prints 20
T2: num = 20;
T2: System.out.println(num); // prints 20
如果doSomething()被同步,它的原子性将得到保证,并且上述场景是不可能的。
答案 1 :(得分:2)
volatile
确保如果你有一个线程A和一个线程B,那么两者都会看到对该变量的任何改变。因此,如果它在某个时刻线程A改变了这个值,那么线程B将来可以看它。
原子操作确保执行所述操作“一步到位”。这有点混乱,因为查看代码'x = 10;'可能看起来是“一步到位”,但实际上需要在CPU上执行几个步骤。原子操作可以通过多种方式形成,其中一种方法是使用synchronized
:
正如您在前面的评论中所提到的,即使您有三个单独的原子步骤,线程A正在某个时刻执行,但是线程B有可能在这三个步骤的中间开始执行。为了确保对象的线程安全性,必须将所有三个步骤组合在一起以完成一个步骤。这是使用锁的部分原因。
需要注意的一件非常重要的事情是,如果要确保两个线程永远不能同时访问您的对象,则必须同步所有方法。您可以在对象上创建一个非同步方法来访问存储在对象中的值,但这会损害类的线程安全性。
您可能对java.util.concurrent.atomic
库感兴趣。我也不是这方面的专家,所以我建议给我推荐一本书:Java Concurrency in Practice
答案 2 :(得分:1)
每个人对volatile变量的读写都是原子的。这意味着线程在读取时不会看到num的值发生变化,但它仍然可以在每个语句之间发生变化。因此,运行doSomething
而其他线程正在执行相同操作的线程将打印10或20,然后再打印10或20.在所有线程完成调用doSomething
之后,num的值将为20。
答案 3 :(得分:0)
我根据Brian Roach的评论修改了我的答案。
它是原子的,因为在这种情况下它是整数。
易变性只能提高线程间的可见性,但不能提供原子能见度。 volatile可以让你看到整数的变化,但不能保证变化中的整合。
例如,long和double会导致意外的中间状态。
答案 4 :(得分:0)
原子操作和同步:
原子执行在单个任务单元中执行,不会受到其他执行的影响。多线程环境中需要进行原子操作以避免数据不规则。
如果我们正在读/写一个int值,那么它是一个原子操作。但通常如果它在方法内部,那么如果方法未同步,则许多线程可以访问它,这可能导致值不一致。但是,int ++不是原子操作。因此,当一个线程读取它的值并将其递增1时,其他线程已读取较旧的值,从而导致错误的结果。
要解决数据不一致问题,我们必须确保count上的递增操作是原子的,我们可以使用Synchronization来实现,但是Java 5 java.util.concurrent.atomic
提供了int和long的包装类,可用于实现此目的原子地不使用同步。
使用int可能会产生数据数据不一致,如下所示:
public class AtomicClass {
public static void main(String[] args) throws InterruptedException {
ThreardProcesing pt = new ThreardProcesing();
Thread thread_1 = new Thread(pt, "thread_1");
thread_1.start();
Thread thread_2 = new Thread(pt, "thread_2");
thread_2.start();
thread_1.join();
thread_2.join();
System.out.println("Processing count=" + pt.getCount());
}
}
class ThreardProcesing implements Runnable {
private int count;
@Override
public void run() {
for (int i = 1; i < 5; i++) {
processSomething(i);
count++;
}
}
public int getCount() {
return this.count;
}
private void processSomething(int i) {
// processing some job
try {
Thread.sleep(i * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
OUTPUT:计数值在5,6,7,8之间变化
我们可以使用java.util.concurrent.atomic
来解决此问题,AtomicInteger
始终将计数值输出为8,因为incrementAndGet()
方法public class AtomicClass {
public static void main(String[] args) throws InterruptedException {
ThreardProcesing pt = new ThreardProcesing();
Thread thread_1 = new Thread(pt, "thread_1");
thread_1.start();
Thread thread_2 = new Thread(pt, "thread_2");
thread_2.start();
thread_1.join();
thread_2.join();
System.out.println("Processing count=" + pt.getCount());
}
}
class ThreardProcesing implements Runnable {
private AtomicInteger count = new AtomicInteger();
@Override
public void run() {
for (int i = 1; i < 5; i++) {
processSomething(i);
count.incrementAndGet();
}
}
public int getCount() {
return this.count.get();
}
private void processSomething(int i) {
// processing some job
try {
Thread.sleep(i * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
会将当前值原子递增1。如下所示:
tooltip