Java多线程与并发编程 | 同步容器与&Atomic包&CAS算法

时间:2021-09-06
本文章向大家介绍Java多线程与并发编程 | 同步容器与&Atomic包&CAS算法,主要包括Java多线程与并发编程 | 同步容器与&Atomic包&CAS算法使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

前言

在多线程环境下我们日常使用的很多类都存在线程安全问题,比如 ArrayList、 HashSet、 HashMap,那么多线程环境下我们应该如何处理好线程问题?CAS算法是什么?除了 synchronized有没有别的方法实现线程安全?乐观锁?悲观锁?

同步容器

JUC工具包中针对这些日常开发中经常使用的集合类,给我们提供了线程安全的类来代替这些类。

  • ArrayList --> CopyOnWriteArrayList -- 写复制列表
  • HashSet --> CopyOnWriteArraySet -- 写复制集合
  • HashMap --> ConcurrentHashMap -- 分段锁映射

CopyOnWriteArrayList

CopyOnWriteArrayList底层通过操作“副本”的形式避免并多线程环境下得并发问题。

当一个线程对 list进行操作时,会先加一把锁,然后将当前对象的值拷贝一份,重新创建一个长度 +1的新对象,然后将引用地址指向新的 ArrayList

add()方法源码

public boolean add(E e) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] elements = getArray();
        int len = elements.length;
        Object[] newElements = Arrays.copyOf(elements, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

CopyOnWriteArraySet

同 CopyOnWriteArrayList类似,只是底层存储数据的数据结构不同

源码

private boolean addIfAbsent(E e, Object[] snapshot) {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        Object[] current = getArray();
        int len = current.length;
        if (snapshot != current) {
            // Optimize for lost race to another addXXX operation
            int common = Math.min(snapshot.length, len);
            for (int i = 0; i < common; i++)
                if (current[i] != snapshot[i] && eq(e, current[i]))
                    return false;
            if (indexOf(e, current, common, len) >= 0)
                    return false;
        }
        Object[] newElements = Arrays.copyOf(current, len + 1);
        newElements[len] = e;
        setArray(newElements);
        return true;
    } finally {
        lock.unlock();
    }
}

ConcurrentHashMap

代码示例

public class ConcurrentHashMapSample {
    public static int users = 100;//同时模拟的并发访问用户数量
    public static int downTotal = 50000; //用户下载的真实总数
    public static ConcurrentHashMap count = new ConcurrentHashMap() ;//计数器
    public static void main(String[] args) {
        ExecutorService executorService  = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(users);
        for(int i = 0 ; i < downTotal ; i++){
            final Integer index = i;
            executorService.execute(()->{
                //通过多线程模拟N个用户并发访问并下载
                try {
                    semaphore.acquire();
                    count.put(index, index);
                    semaphore.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();//关闭调度服务
        System.out.println("下载总数:" + count.size());
    }
}

ConcurrentHashMap底层将原始数据切分成一个一个小的 segment,长度为 2的 n次方,然后分段加锁,多个线程同时处理,在保证安全的状态下也提升了一些效率。

扩展

原子性

原子性是指一个操作或多个操作要么全部执行,且执行的过程不会被任何因素打断,要么就都不执行。

Atomic包

  • Atomic包是 JUC下得另一个专门为线程安全设计的 Java包,包含多个原子操作类
  • Atomic常用类
    • AtomicInteger
    • AtomicIntergerArray
    • AtomicBoolean
    • AtomicLong
    • AtomicLongArray

通过 synchronized关键字保证下载量在多线程下的累加安全。这里可以通过更加轻量级的方法,利用 Atomic原子类完成优化。

优化代码

public static AtomicInteger count = new AtomicInteger() ;//计数器
...
//线程不安全
public static void add(){
    count.getAndIncrement(); //count++
}

CAS算法

悲观锁

  • 锁是用来做并发最简单的方式,当然其代价也是最高的。独占锁是一种 悲观锁, synchronized就是一种 独占锁,它假设最坏的情况,并且只有在确保其它线程不会造成干扰的情况下执行,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。

乐观锁

  • 所谓 乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。其中 CAS(比较与交换, Compare And Swap) 是一种有名的无锁算法。

Atomic的应用场景

虽然基于 CAS的线程安全机制很好很高效,但要说的是,并非所有线程安全都可以用这样的方法来实现,这只适合一些粒度比较小型,如计数器这样的需求用起来才有效,否则也不会有锁的存在了。

原文地址:https://www.cnblogs.com/chenguopa/p/15235728.html