线程安全集合

jdk 5 引入并发包

概要

1. 线程安全的集合

  • 遗留的线程安全集合,如 Hashtable, Vector
  • Collections 里的一系列以 synchronized 开头的方法,可以把非线程安全的集合包装成线程安全的集合
    • 体现了设计模式中的装饰器模式
List list = new ArrayList();
// 装饰器模式
List list1 = Collections.synchronizedList(list);
  • juc 下的线程安全集合
    • CopyOnWrite 开头的集合,采用了“写入时拷贝”的思想来提高并发度
    • Concurrent 支持并发(线程安全)的集合
    • Blocking 支持阻塞操作

2. CopyOnWriteArrayList -> Vector

支持多线程并发读取 (get, 遍历) ,只支持单线程写入 (add, remove)

当有修改操作发生时,会对原有的数组进行拷贝,拷贝出一个新的数组,修改操作在新的数组上发生,修改加锁。

原有数组的查询操作不需要加锁保护,当修改线程执行完毕,再用新的数组替换原有数组。

把读写操作分离开,读不加锁,写加锁,用空间换取读不加锁

适合读多写少的场景

3. ConcurrentHashMap -> Hashtable

1.7 以前
分段锁

  • 首先是一个 segment的数组, 每个segment的元素又是一个 HashEntry 数组,Node数组中
    又存放了链表
  • 锁定以 segment 为单位
  • 初始化时,会创建所有segment数组的元素,而且 segment数组不能扩容,占用内存多
  • put 操作会锁住key 对应的 segment,元素放入 HashEntry 的数组+链表结构中,元素放入链表头
  • get 操作无锁
  • 扩容会加锁
  • 计算个数

1.8 又做了修改
Hashtable 是锁住了整个map集合,而 ConcurrentHashMap,它只会锁住map集合中的一个桶,根据桶的多少,可以进一步提高并发度,只要读写操作落在不同的桶里,操作就可以并行执行

  • 初始化数组时,懒惰初始化
  • 当容量小于64首先尝试扩容,当超过这个容量并且链表大于8
    会将链表树化,树化过程中会锁住链表头
  • put 操作会锁住链表头,新加的元素放入链表尾部
  • get 操作不需要加锁,仅需要用cas保证元素的可见性
  • 扩容 以链表为单位扩容,当扩容时有多个线程来同时访问,这些线程会协助扩容

4. ConcurrentSkipListMap

  • 类似于之前 LinkedHashMap ,都可以保持元素遍历的顺序和放入的顺序是一致的。
    LinkedHashMap 非线程安全
    ConcurrentSkipListMap 线程安全

数据结构 跳跃表

5. BlockingQueue 阻塞队列

队列, 先进先出 FIFO
经常用来实现生产消费模式,来解耦生产者、消费者线程

BlockingQueue<Item> queue = new LinkedBlockingDeque<>(5);
Thread t1 = new Thread(()-> {
    try {
        for (int i = 0; i < 6; i++) {
            Item item = new Item("产品" + i);
            queue.put(item);
            System.out.println(Thread.currentThread().getName() + "生产了商品:" + item.name);
            Thread.sleep(1000);
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
Thread t2 = new Thread(()-> {
    try{
        for (int i = 0; i < 0; i++) {
            Item item = queue.take();
            System.out.println(Thread.currentThread().getName() + "消费了商品:" + item.name);
        }
    } catch (Exception e) {

    }
});

6. cas

  • cas 乐观锁(无锁并发), 不断重试直到成功,体现的是乐观的精神
  • synchronized 悲观锁 , 同一时刻,只能有一个线程访问 synchronized 代码块的内容
Object object = new Object();
//  线程2 
synchronized(object) {
    // 代码片段1
    // 代码片段2
}
// 线程1

线程1 假定没有别的线程干扰, 就直接执行完这些代码
但如果执行过程有其他线程干扰,之前结果作废,重试一遍,如果不成功继续重试直到成功

            线程2   cpu
            代码片段1
            代码片段2

代码片段1 cpu
代码片段2 cpu

compareAndSwap(旧值,新值) 当旧值被别的线程改变,就认为这次操作失败,就进行重试。如果旧值没有被别的线程改变就认为这次操作成功

应用场景:

  • 原子操作类 例如 AtomicInteger,AtomicBoolean… 它们内部用的都是 cas 的无锁并发
  • 适用于并发量较小,多cpu情况

7. volatile

配合 cas 编程时,共享变量的声明上必须用 volatile 因为要读取主存中最新的结果

Lock与Synchronzied

在 jdk 1.5 时引入了 ReentrantLock 使用了队列和cas的功能实现加锁解锁,性能上在当时是优于 synchronized 的
在 jdk 1.6 对 synchronzied 底层代码做了优化, 引入了偏向锁,轻量级锁,重量级锁,并发低时性能其实 比 ReentrantLock 更好

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值