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到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,
在修改完之后的某一个时刻(线程退出之前),自动把线程变量副本的值回写到对象在堆中变量。这样在堆中的对象的值就产生变化了。