四时宝库

程序员的知识宝库

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

前言

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

同步容器

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

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

CopyOnWriteArrayList

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

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

add()方法源码

Bash
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类似,只是底层存储数据的数据结构不同

源码

Bash
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

代码示例

Bash
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原子类完成优化。

优化代码

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

CAS算法

悲观锁

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

乐观锁

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

Atomic的应用场景

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


作者:申阳
链接:https://juejin.cn/post/7000746878178918414
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

发表评论:

控制面板
您好,欢迎到访网站!
  查看权限
网站分类
最新留言
    友情链接