zoukankan      html  css  js  c++  java
  • 并发包 concurrent(一) Atomic

    1:基础概念

    悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。

    乐观锁(Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。

    原子操作:所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程),是不需要synchronized,通常情况下,在Java里面,++i或者--i不是线程安全的,这里面有三个独立的操作:获得变量当前值,为该值+1/-1,然后写回新的值。在没有额外资源可以利用的情况下,只能使用加锁才能保证读-改-写这三个操作是“原子性”的。

    例如下面的例子 IncrementAndGet类中的next()方法是一个自增1操作,为了保证线程安全加了synchronized关键字

    public class IncrementAndGet() {
        private int value;
        public synchronized int next(){
          return value++;
        } 
    }

    这种加锁的方式属于悲观锁的方式,效率太低。可以采用例外一种方式处理:

    1. 从内存中读取value 值,假设为10, 我们把这个值称为A

    2. B = A+1 得到 B = 11

    3. 用A 和 内存的值相比, 如果相等(就是说在过去的一段时间,没人修改内存的值), 那就把B的值(11)写入内存,  如果不相等(就是说过去的一段时间, 有人修改了内存value 的值), 意味着A已经不是最新值了, 那就放弃这次修改, 跳回第1步去”

    第三步其实就是一条硬件指令,保证原子执行。 在单个CPU上就不用说了,如果是有多个CPU, 这个指令甚至会锁住总线, 确保同一时刻只有一个CPU能访问内存!

    final AtomicInteger value = new AtomicInteger(10);
        @Test
        public final int test1(){
            for(;;){
                int current = value.get();//获取当前值  
                int next = current+1; //设置期望值
                if(value.compareAndSet(current, next)){
                    return next;
                }
            }
        }

    AtomicInteger类compareAndSet通过原子操作实现了CAS操作,最底层基于汇编语言实现。

    CAS是Compare And Set的一个简称,如下理解:

    1,已知当前内存里面的值current和预期要修改成的值new传入

    2,内存中AtomicInteger对象地址对应的真实值(因为有可能别修改)real与current对比,

          相等表示real未被修改过,是“安全”的,将new赋给real结束然后返回;不相等说明real已经被修改,结束并重新执行1直到修改成功

    我们仔细地审视这段代码, 它根本没有加锁, 其他线程都可以进入next()方法, 读取数据,操作数据, 最后使用CAS来决定这次操作是否有效, 如果内存值被别人改过,那就再次循环尝试,这就采用了乐观锁的方式。

     为了说明AtomicInteger的原子性,这里代码演示多线程对一个int值进行自增操作,最后输出结果,代码如下:

    public class AtomicIntegerDemo {
    
        private static AtomicInteger atomicInteger = new AtomicInteger(0);
    
        public static void main(String[] args){
            for (int i = 0; i < 5; i++){
                new Thread(new Runnable() {
                    public void run() {
                        //调用AtomicInteger的getAndIncement返回的是增加之前的值
                        System.out.println(atomicInteger.getAndIncrement());
                    }
                }).start();
            }
            System.out.println(atomicInteger.get());
        }
    }

    输出结果如下:

    0
    2
    1
    3
    4
    4
    

    原子更新数组

    通过原子更新数组里的某个元素,共有3个类:

    • AtomicIntegerArray:原子更新整型数组的某个元素
    • AtomicLongArray:原子更新长整型数组的某个元素
    • AtomicReferenceArray:原子更新引用类型数组的某个元素

    AtomicIntegerArray常用的方法有:

    • int getAndSet(int i, int delta):以原子方式将输入值与数组中索引为i的元素相加
    • boolean compareAndSet(int i, int expect, int update):如果当前值等于预期值,则以原子方式更新数组中索引为i的值为update值
    public class AtomicIntegerArrayDemo {
        static int[] value = new int[]{1, 2};
        static AtomicIntegerArray ai = new AtomicIntegerArray(value);
    
        public static void main(String[] args){
            ai.getAndSet(0,3);
            System.out.println(ai.get(0));
            System.out.println(value[0]);
        }

    运行结果是:

      3 
      1

    数组value通过构造的方式传入AtomicIntegerArray中,实际上AtomicIntegerArray会将当前数组拷贝一份,所以在数组拷贝的操作不影响原数组的值。

    原子更新引用类型

    需要更新引用类型往往涉及多个变量,早atomic包有三个类:

    • AtomicReference:原子更新引用类型
    • AtomicReferenceFieldUpdater:原子更新引用类型里的字段
    • AtomicMarkableReference:原子更新带有标记位的引用类型。

    下面以AtomicReference为例进行说明:

    public class AtomicReferenceDemo {
    
        static class User{
            private String name;
            private int id;
    
            public User(String name, int id) {
                this.name = name;
                this.id = id;
            }
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
    
            public int getId() {
                return id;
            }
    
            public void setId(int id) {
                this.id = id;
            }
        }
    
        public static AtomicReference<User> ar = new AtomicReference<User>();
    
        public static void main(String[] args){
            User user = new User("aa",11);
            ar.set(user);
            User newUser = new User("bb",22);
            ar.compareAndSet(user,newUser);
            System.out.println(ar.get().getName());
            System.out.println(ar.get().getId());
        }
    }

    运行结果为:

    bb 
    22

    可以看到user被成功更新。

    原子更新字段类

    如果需要原子更新某个类的某个字段,就需要用到原子更新字段类,可以使用以下几个类:

    • AtomicIntegerFieldUpdater:原子更新整型字段
    • AtomicLongFieldUpdater:原子更新长整型字段
    • AtomicStampedReference:原子更新带有版本号的引用类型。

    要想原子更新字段,需要两个步骤:

    1. 每次必须使用newUpdater创建一个更新器,并且需要设置想要更新的类的字段
    2. 更新类的字段(属性)必须为public volatile

    下面的代码演示如何使用原子更新字段类更新字段:

    public class AtomicIntegerFieldUpdaterDemo {
    
        //创建一个原子更新器
        private static AtomicIntegerFieldUpdater<User> atomicIntegerFieldUpdater =
                AtomicIntegerFieldUpdater.newUpdater(User.class,"old");
    
        public static void main(String[] args){
            User user = new User("Tom",15);
            //原来的年龄
            System.out.println(atomicIntegerFieldUpdater.getAndIncrement(user));
            //现在的年龄
            System.out.println(atomicIntegerFieldUpdater.get(user));
        }
    
        static class User{
            private String name;
            public volatile int old;
    
            public User(String name, int old) {
                this.name = name;
                this.old = old;
            }
    
            public String getName() {
                return name;
            }
    
            public void setName(String name) {
                this.name = name;
            }
    
            public int getOld() {
                return old;
            }
    
            public void setOld(int old) {
                this.old = old;
            }
        }
    }

    输出的结果如下:

    15 
    16

  • 相关阅读:
    基于JavaFXWJFXGameEngine游戏引擎介绍与进度
    进程线程与cpu绑定
    [Vim练级攻略] Vim基础操作
    hdu 2159 fate
    python的httplib注意事项
    SQL优化总结
    项目整体开发流程以及配置人员
    hdu 1010 解题报告 Tempter of the Bone
    在centos搭建git服务器时,不小心把/home/git目录删除了,我是怎么恢复的
    int 和bigint差别有多大?
  • 原文地址:https://www.cnblogs.com/myseries/p/8627274.html
Copyright © 2011-2022 走看看