• 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到线程本地内存中,建立一个变量副本,之后线程就不再和对象在堆内存变量值有任何关系,而是直接修改副本变量的值,

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

  • 相关阅读:
    struts2接收参数的几种形式
    oracle merge into函数中插入clob字段
    程序员能力矩阵
    spring mvc工作原理
    struts2核心和工作原理
    mysql主从复制(windows下)
    mysql主从复制(linux下)
    spring 注解事务
    异常错误集锦
    Redis 作为缓存服务器的配置
  • 原文地址:https://www.cnblogs.com/sanhuan/p/4736988.html
Copyright © 2020-2023  润新知