小白架构师成长之路17-并发编程atomic&collections详解

并发编程atomic&collections详解

大家里面的案例可以从gitHub下载下来自己看一下
地址:https://github.com/JolyouLu/Juc-study.git 代码在Juc-Atominc下

Atominc简介

在之前的多线程学习中,我们人使用到了Volatile他能保证一致性和有序性,但是不能保证原子性,我们可以使用加锁来确保操作的原子性,加锁还是对程序运行速度会有一定的影响,锁加的不对会极大的影响程序速度,那我们如何在不加锁的情况下确保原子性操作呢,我们的java.util.concurrent.atomic包下的类,都是可以无锁进行原子性操作,底层原理应用到了CAS操作

在这里插入图片描述

原子操作类归纳

类型 类名
基本类型 AtomicBoolean
基本类型 AtomicInteger
基本类型 AtomicLong
数组 AtomicIntergerArray
数组 AtomicLongArray
数组 AtomicReferenceArray
引用类型 AtomicReference
引用类型 AtomicReferenceArrayFleldUpdater
原子更新字段类 AtomicIntegerFieldUpdater
原子更新字段类 AtomicLongFieldUpdater
原子更新字段类 AtomicStampedFieldUpdater

AtomicInteger

AtomicInteger是对int类型的一个封装,提供原子性的访问和更新操作是我们工作中最常用的一个原子类,一般用于并发统计。常见的api有

java.util.concurrent.atomic.AtomicInteger#incrementAndGet 原子自增1

java.util.concurrent.atomic.AtomicInteger#decrementAndGet原子自减1

java.util.concurrent.atomic.AtomicInteger#addAndGet原子在当前值加上给定的值。

java.util.concurrent.atomic.AtomicInteger#get返回当前最新的值

CAS算法

什么是CAS算法,CAS(Compare-And-Swap)即比较与交换,CAS算法是硬件对于并发操作提供的数据共享支持,CAS的变量都带有Volatile保证了可见性,CAS怎么保证原子性呢,CAS中包含了三个操作数:内存值 V,预估值 A,更新值 B,只有V == A 时,CPU会尝试把B赋值到V上,否则不做任何操作,大致意思就是首先去获取当前内存中的值V,在我修改之前我再去获取一次内存放到A,那如果这是V == A 那意思就是内存中的值还没修改这时我只需要把B(新值)写入主内存即可完成修改了,因为B回写主内存只有一步所以可以保证原子性。

案例

public class OrdersOrdersInteger {
    //原子性操作类
    static AtomicInteger count = new AtomicInteger(0);
    //普通int类
//    static Integer count = new Integer(0);

    public String getOrdersNo(){
        SimpleDateFormat data = new SimpleDateFormat("YYYYMMDDHHMMSS");
        //保证原子性 不会有重复订单id
        return data.format(new Date())+count.incrementAndGet();
        //无法保证原子性递增 出现生产重复订单id
//        return data.format(new Date())+count++;
    }

    public static void main(String[] args) {
        final CountDownLatch latch = new CountDownLatch(1);
        ExecutorService exeuctor = Executors.newFixedThreadPool(10);
        final OrdersOrdersInteger orderServer=new OrdersOrdersInteger();
        for (int i =0;i<10;i++){
            exeuctor.submit(new Runnable() {
                @Override
                public void run() {
                    System.out.println(orderServer.getOrdersNo());;
                }
            });
        }
        latch.countDown();
        exeuctor.shutdown();
    }
}

ABA问题

ABA问题就是,假设我现在主内存有一个int=10,然后线程1和2需要对这个值进行运算,但是有点不同的是线程1需要加10后减10,然后线程2需要加10,然后线程1和2同时拷贝当前内存的值10作为副本放入A(预估值)中,线程1比程序2快一点的对int进行了加10,减10然后回写主内存当前主内存为10,然后线程2进行了加10放入B,获取当前V(内存值),比较一下自己之前拿到的A(预估值),发现都是10如果把B赋值给V,这中间就存在一个ABA的问题,线程1对内存进行了2次操作后得到10回写到主内存中了,然后线程2修改比较V和A的时候,看似相同都是10,其实不一样现在的V(被线程1修改2次的),A(初始值),其实线程2要发起当前操作重新获取A值进行运算比对V,但是CPU并不知道,CPU只知道10=10可以修改

以下是模拟ABA问题

public class AtomicIntegerABAExample {
  private static AtomicInteger atomicInt = new AtomicInteger(100);


  public static void main(String[] args) throws InterruptedException {
    Thread intT1 = new Thread(new Runnable() {
      @Override
      public void run() {
        //线程1对 修改100为101后又修改101为100
        atomicInt.compareAndSet(100, 101);
        atomicInt.compareAndSet(101, 100);
      }
    });

    Thread intT2 = new Thread(new Runnable() {
      @Override
      public void run() {
        try {
          TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
        }
        //线程2修改 100为101成功,正常情况我们是不允许成功的因为V值和A值的值虽然相同但是 他们本质是不同的
        boolean c3 = atomicInt.compareAndSet(100, 101);
        System.out.println(c3); // true
        System.out.println(atomicInt.get());
      }
    });
    intT1.start();
    intT2.start();
    intT1.join();
    intT2.join();
  }
}

ABA问题的解决方法

经过上面问题分析,解决ABA问题我们可以使用一个版本号标记当前版本,每次更新后版本会+1,如果线程,即主内存int=10,线程12进入运算获取当前内存副本放A中,如果线程1运算了2次 加10 减10,当前主内存一个是10 (版本2),然后线程2进行进行加10操作,比较V=10(版本2),和自己的A=10(版本0),虽然数字一样但是版本不同所以这个加10操作是不会被更新,线程要放弃本次操作,重新取值运算

public static void main(String[] args) throws InterruptedException {
    Thread refT1 = new Thread(new Runnable() {
        @Override
        //线程1对 修改100为101后又修改101为100 每次修改会版本会加一
        public void run() {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
            }
            atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
            atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
        }
    });

    Thread refT2 = new Thread(new Runnable() {
        @Override
        //线程2对 修改100为101,但是比较的是版本0的值
        public void run() {
            int stamp = atomicStampedRef.getStamp();
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
            }
            boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1);
            System.out.println(c3); // 修改失败false
        }
    });
    refT1.start();
    refT2.start();

}
}

Collections简介

HashMap(线程不安全)

为什么HashMap线程不安全执行如下案例

public class HashMapExample {
    public static final Map<String,String> map = new HashMap<>();

    public static void main(String[] args) {
        //线程1 插入0-1000
        new Thread(){
            public void run(){
                for (int i=0;i<1000;i++){
                    map.put(String.valueOf(i),String.valueOf(i));
                }
            }
        }.start();

        //线程2 插入1000-2000
        new Thread(){
            public void run(){
                for (int j=1000;j<2000;j++){
                    map.put(String.valueOf(j),String.valueOf(j));
                }
            }
        }.start();

        try {
            Thread.currentThread().sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //输出
        for(int i=0;i<2000;i++){
            System.out.println("第:"+i+"元素,值:"+map.get(String.valueOf(i)));
        }
    }
}

执行代码后我们查看输出的时候会发现有的key对应的value是null的,这个原因是因为map在put时候发现容量不够需要扩容的时候需要重新编排哈希,然后这时你去获取他哈希的位置就是空的了

HashTable&ConcurrentHashMap(线程安全)

HashTable&ConcurrentHashMap的相同点是线程安全的,不同点就是锁的方式不一样

HashTable

我们使用HashTable运行如下代码发现并没有任何问题

public class HashTableExample {
    public static final Map<String, String> map = new Hashtable<>();

    public static void main(String[] args) {
        new Thread(){
            public void run(){
                for (int i = 0;i<1000;i++){
                    map.put(String.valueOf(i),String.valueOf(i));
                }
            }
        }.start();

        new Thread(){
            public void run() {
                for(int j=1000;j<2000;j++){
                    map.put(String.valueOf(j), String.valueOf(j));
                }
            }
        }.start();

        try {
            Thread.currentThread().sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //输出
        for(int i=0;i<2000;i++){
            System.out.println("第:"+i+"元素,值:"+map.get(String.valueOf(i)));
        }
    }
}

HashTable中的put方法是加了重量级锁synchronized(不推荐用性能比较低)

ConcurrentHashMap

public class ConcurrentHashMapExample {
    public static final Map<String, String> map = new ConcurrentHashMap<>();

    public static void main(String[] args) {
        new Thread(){
            public void run(){
                for (int i = 0;i<1000;i++){
                    map.put(String.valueOf(i),String.valueOf(i));
                }
            }
        }.start();

        new Thread(){
            public void run() {
                for(int j=1000;j<2000;j++){
                    map.put(String.valueOf(j), String.valueOf(j));
                }
            }
        }.start();

        try {
            Thread.currentThread().sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //输出
        for(int i=0;i<2000;i++){
            System.out.println("第:"+i+"元素,值:"+map.get(String.valueOf(i)));
        }
    }
}

ConcurrentHashMap中的put方法使用了分段锁性能比较因为锁的颗粒度更小了所以性能更优

ArrayList(线程不安全)

ArrayList之所以线程不安全和HashMap原理差不多,ArrayList在add的时候需要扩容,需要重新索引

CopyOnWriteArrayList(线程安全)

读写分离、读多写少 黑白名单

步骤:

  1. 如果写操作未完成,那么直接读取原数组的数据
  2. 如果写操作完成,但是引用还未指向新数组,那么也是读取原数组数据
  3. 如果写操作完成,并且引用已经指向了新的数组,那么直接从新数组中读取
发布了33 篇原创文章 · 获赞 22 · 访问量 952

猜你喜欢

转载自blog.csdn.net/weixin_44642403/article/details/104344028
今日推荐