多线程读书笔记(三)

并发容器-写时复制的list和Set

CopyOnWriteArrayList

基于synchronized的同步容器有问题,其中一个就是复合操作饿时候(比如先检查再更新),也需要调用方加锁。
CopyOnWriteArrayList的内部也是一个数组,每次修改操作,都会新建一个数组,复制原数组的内容到新数组,在新数组上进行需要的修改,然后以原子方式设置内部的数组引用,这就是写时复制
一句话:数组内容是只读的,写操作都是通过新建数组,然后原子性的修改数组引用来实现的。
读不需要锁,可以并行。读写也可以并行,但是只允许一个写线程。


    /** The lock protecting all mutators */
    final transient ReentrantLock lock = new ReentrantLock();

    /** The array, accessed only via getArray/setArray. */
    private transient volatile Object[] array;

来看 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();
        }
    }

以优化读的操作为目标,在优化读的同时牺牲了写的性能。
保证线程安全的两种思路,一种是锁,使用synchronized或者ReentrantLock,另外一种就是循环CAS。写时复制提现了保证线程安全的另外一种思路
**锁和循环CAS都是控制对同一个资源的访问冲突,而写时复制通过复制资源减少冲突。**适合读多,写少的场合,是一个很好的解决方案。

CopyOnWriteArraySet

看源码可知,CopyOnWriteArraySet是基于CopyOnWriteArrayList实现了,和HashSet和TreeSet相比,性能低下

简单总结:写时复制,是计算机程序中一种重要的思维和技术。

ConcurrentHashMap

特点1-并发安全,直接支持一些原子复合操作,2,-支持高并发,读操作并行,写操作支持一定程度的并行。3-与同步容器synchronizedMap相比,迭代不用加锁,不会抛出ConcurrentModificationException,4,-弱一致性

Java7 hashMap 多线程下,会出现死循环问题(链表头插法的问题)
java8 修改为尾插法了,但是会出现并发修改数据被覆盖的情况

参考这个

接口:java.util.concurrent.ConcurrentMap
在这里插入图片描述
ConcurrentHashMap实现高并发的基本机制

  • 分段锁
  • 读不需要锁
    ConcurrentHashMap采用分段锁技术,将数据分为多个段,而每个段有一个独立的锁。
    ConcurrentHashMap弱一致性
    ConcurrentHashMap的迭代器创建后,就会按照哈希表结构遍历每个元素,但是在遍历过程中,内部元素可能会发生变化,如果变化发生在已经遍历过的部分,迭代器就不会反映出来,否则,将反映出来。

基于跳表的map和set

java并发包中与TreeMap和TreeSet对应的并发版本是:ConcurrentSkipListMap和ConcurrentSkipListSet。
特点
1-没有使用锁,所有操作都是无阻塞的。所有操作都是可以并行,包括写
2-弱一致性。一些方法不是原子的,比如 putAll 以及clear

跳表是基于链表的,在链表的基础上加了多层索引结构。

ConcurrentSkipListMap 为了实现并发安全,高效,无锁非阻塞,实现非常复杂。

并发队列

无锁非阻塞:主要通过循环CAS实现并发安全。
阻塞队列:这些队列都使用了锁和条件。很多操作都是需要先获取锁或者满足特定条件,获取不到锁,会等待。

ConcurrentLinkedQueue的算法基于一篇论文,感兴趣的话,看下面链接,参考别人的博客

普通阻塞队列

注意:所有普通阻塞队列都是基于:ReentrantLock和显式条件Condition实现的
例子:ArrayBlockingQueue的实现:一个数组存储元素,有不满和不空两个条件用于协作,和用显式条件实现生产者消费者模式类似

异步任务执行服务

目的:将执行任务和提交任务分开来。,需要理解这种思维。
Runable和Callable --表示要执行的异步任务
Executor和ExecutorService – 表示执行服务
Future :表示异步任务的结果

Runable没有返回结果,且不会抛出异常。Callable则都会。

线程池

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {

使用过程注意点:
1-如果是有界队列,maxPoolSize 是无限的,则会创建过多的线程,占满CPU和内存
2-如果是无界队列,服务不了的task,总是在排队,导致内存占用过多。
注意:在任务量非常大的场景下,让拒绝策略有机会执行,是保证系统稳定的一个方面

线程池的死锁:线程池任务之间有依赖,可能会出现死锁。
例子:任务A,在执行过程中,给同样的任务执行服务提交了一个任务B,但需要等待任务B结束。
解决:使用SynchronousQueue

多线程情况下,优先使用线程池

定时任务

应用场景:
1-闹钟程序或者任务提醒
2-监控系统。比如每一段时间采集一下系统数据,对异常事件报警
3-统计系统。凌晨一定时间统计一下昨日的各种数据指标

Java实现方式
Timer和TimerTask,以及JUC包下的 ScheduledExecutorService

/**TimerTask 延时任务
 * @author root
 */
public class TimerFixedDelay {
    static class LongRunningTask extends TimerTask{

        @Override
        public void run() {

            try {
                Thread.sleep(5_000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("long running finished");
        }
    }
    static class FixedDelayTask extends TimerTask{

        @Override
        public void run() {
            System.out.println(System.currentTimeMillis());
        }
    }

    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new LongRunningTask(),10);
        timer.schedule(new FixedDelayTask(),1000);
    }
}

Timer内部主要由任务队列和Timer线程两部分组成,任务队列是一个基于堆实现的优先级队列,按照下次执行的时间排优先级。需要强调的是:一个Timer对象只有一个Timer线程。这也意味着,定时任务不能耗时太长,更不能无限循环

/**
 * Timer死循环
 * */
public class EndlessLoopTimer {
    static class LoopTask extends TimerTask{

        @Override
        public void run() {
            while (true){
                try {
                    // 模拟执行任务
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        
    }
    
    // 永远没有机会执行
    static class ExampleTask extends TimerTask{

        @Override
        public void run() {
            System.out.println("hello");
        }
    }


    public static void main(String[] args) {
        Timer timer = new Timer();
        timer.schedule(new LoopTask(),10);
        timer.schedule(new ExampleTask(), 100);
        
    }
}

第一个定时任务是一个无限循环,其后的定时任务,exampleTask将没有执行的机会。
非常重要的一点:当执行任何一个任务的run方法时,一旦run抛出异常,Timer线程会退出,从而所有定时任务都会取消

总结:Timer以及TimerTask不建议使用

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值