java多线程中关于原子操作

各种不同的情况保证数据的正确性,完整性。

public class TestMultiThread implements Runnable {
    private static int i;
    private static volatile Integer vi = 0;
    private static AtomicInteger ai = new AtomicInteger();
    private static Integer si = 0;
    private static Integer ia = 1;
    private static int ri;
    private static AtomicInteger flag = new AtomicInteger();
    private static Lock lock = new ReentrantLock();   //如果lock不加static关键字时,那么每一个线程将锁住的是具体的对象,
                                                      //而不是这个类。就会照成数据的不正确性

    @Override
    public void run() {
        for (int k = 0; k < 200000; k++) {
            i++;    //不是原子操作,此处含有三步(取得i的值,i+1,把i+1后的结果赋值给i)结果将有异常。当一条线程执行到i+1时,还没有赋值,另一线程i+1并赋值。
            vi++;    //同上,②解释
            ai.incrementAndGet();    //可以用原子方式更新的 int 值。以原子方式将当前值加 1.它只有一步操作,随便哪个线程执行都将只有这一步。
        
            // synchronized(si){
                                        // 此方法会照成si的值不对,是应为同步语句锁定的是对象,用Integer作为对象锁来使用,这本身没有任何问题。
            // si++;                    // 但同步体里的操作偏偏是更改当前的锁对象,结果就是前的对象锁其实已经改变了,不是针对唯一的一个对象加锁,所以会出现问题。
                                        // 也因此,换一个公共对象就能解决问题。
                                        // 在++操作中其实有一个赋值操作,这时对象已经改变,或者说是非原子操作        
            // }
            synchronized (ia) {   //用不变的ia作为锁对象就能保证数据的正确性
                si++;
            }
            lock.lock();   //此处的lock锁必须为static的
            try {
                ri++;
            } finally {
                lock.unlock();
            }
        }
        flag.incrementAndGet();
    }

    public static void main(String[] args) throws InterruptedException {
        TestMultiThread t1 = new TestMultiThread();
        TestMultiThread t2 = new TestMultiThread();
        ExecutorService exec1 = Executors.newCachedThreadPool();
        ExecutorService exec2 = Executors.newCachedThreadPool();
        exec1.execute(t1);
        exec2.execute(t2);
        while (true) {
            if (flag.intValue() == 2) {
                System.out.println("i>>>>>" + i);
                System.out.println("vi>>>>>" + vi);
                System.out.println("ai>>>>>" + ai);
                System.out.println("si>>>>>" + si);
                System.out.println("ri>>>>>" + ri);
                break;
            }
            Thread.sleep(50);
        }
    }
}

结果:

i>>>>>398950

vi>>>>>386295

ai>>>>>400000

si>>>>>400000

ri>>>>>400000

解释②:

在 java 垃圾回收整理一文中,描述了jvm运行时刻内存的分配。其中有一个内存区域是jvm虚拟机栈,每一个线程运行时都有一个线程栈,

线程栈保存了线程运行时候变量值信息。当线程访问某一个对象时候值的时候,首先通过对象的引用找到对应在堆内存的变量的值,然后把堆内存

变量的具体值load到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,

在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。