并发容器类和并发控制工具类与forkjoin解析

1、ArrayList

arrayList是由一维数组组成的,其 无参时,初始化为length=0的数组
可以通过其源码知道:

    /**
     * Constructs an empty list with an initial capacity of ten.
     */
    public ArrayList() {
        this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
    }
        private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
    /**
     * Appends the specified element to the end of this list.
     *
     * @param e element to be appended to this list
     * @return <tt>true</tt> (as specified by {@link Collection#add})
     */
    public boolean add(E e) {
        ensureCapacityInternal(size + 1);  // Increments modCount!!debugger知道初始化size为0
        elementData[size++] = e;
        return true;
    }


arrayList内部运行方式如下:
add方法 =》 用扩容的方式,来实现ArrayList
0=》10 =》 15 =》 22 =》 33 第一次变为10,每次扩容为原来的1.5倍

扩容原理:
//创建一个新的数组,长度为newCapacity,
//创建完后,把旧的数组,elementData,拷贝进来
elementData = Arrays.copyOf(elementData, newCapacity);

数据这个时候,double了,旧的数组会怎么样? 会被回收如下代码解析

  private void ensureCapacityInternal(int minCapacity) {
        if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {//如果数组等于默认空数组长度为0赋值最小的容量10
            minCapacity = Math.max(DEFAULT_CAPACITY, minCapacity);
        }

        ensureExplicitCapacity(minCapacity);//扩容
    }
    private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

   private void ensureExplicitCapacity(int minCapacity) {
        modCount++;

        // overflow-conscious code
        if (minCapacity - elementData.length > 0)//如果最小容量减去数组长度大于0就扩容
            grow(minCapacity);
    }

   /**
     * Increases the capacity to ensure that it can hold at least the
     * number of elements specified by the minimum capacity argument.
     *
     * @param minCapacity the desired minimum capacity
     */
    private void grow(int minCapacity) {
        // overflow-conscious code
        int oldCapacity = elementData.length;  //旧的数组长度
        int newCapacity = oldCapacity + (oldCapacity >> 1);//设置一个新的容量为旧的容量+旧的容量的1/2
        if (newCapacity - minCapacity < 0)//初始新值小于最小容量
            newCapacity = minCapacity;//赋值新的容量等于最小容量
        if (newCapacity - MAX_ARRAY_SIZE > 0)//新的容量大于再打容量
            newCapacity = hugeCapacity(minCapacity);//
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);//将旧的数组复制给新的数组
    }
        private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

扩容性能很差,怎么办?
为了避免扩容,你在使用的时候,可以指定一个比较好的capacity

2、CopyOnwrite思想

互斥锁并发度很低
一句话概括读写锁的 作用:提高互斥锁的性能

互斥锁的问题:
1、一个读很多的时候,写容易被饿死。
2、假如拿到写锁?大量的读就被阻塞,不希望看到这样的情况

怎么解决这个问题呢? CopyOnWrite
写的时候,拷贝一份数据出来,修改完后才切换引用,读取新的对象
优点:写时,仍然可以读,并且读到的不是脏数据

问题:数据存在一定的延时,导致内存翻倍,很容易OOM

每次写,都复制,浪费资源吗?
会,以空间换时间。
大量的读,非常少量的写

写的时候要加锁,同一时刻只允许一个写进行

内部原理:
Arrays.copyOf(elements, len + 1);

3、set

Set 实际上是数学概念,集合: 无序的、不重复
有序可以看做无序的特殊情况

HashSet数据到底有没有顺序
实现的时候,没有刻意打乱顺讯,也没有可以保证顺序,顺序没有保障

HashSet 使用 HashMap实现的,怎么实现的呢?
利用HasmMap 的key不重复特性来实现的源代码如下:

   private class EntrySet extends AbstractSet<Map.Entry<K,V>> {
        public Iterator<Map.Entry<K,V>> iterator() {
            return getIterator(ENTRIES);
        }

        public boolean add(Map.Entry<K,V> o) {
            return super.add(o);
        }

CopyOnWriteArraySet Set不能重复

  return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
            addIfAbsent(e, snapshot);//indexOf返回大于0值就false否则就新增
 private static int indexOf(Object o, Object[] elements,
                               int index, int fence) {
        if (o == null) {
            for (int i = index; i < fence; i++)
                if (elements[i] == null)
                    return i;
        } else {
            for (int i = index; i < fence; i++)
                if (o.equals(elements[i])) //判断其数组有相同的元素返回该坐标
                    return i;
        }
        return -1;
    }

CopyOnWriteArrayList list可以重复
起源吗解析同理在这里就不解析了。
al.addIfAbsent(e); //利用这个方法来解决重复问题


ConcurrentSkipListSet Set,数据的顺序,是真的得到了保障
ConcurrentSkipListMap实现的

4、队列queue

Queue (放满元素:添加元素阻塞方法线程就会阻塞,一直等到有空余的位置,把元素放进去,才会结束阻塞 非阻塞的方法,线程直接忽略,如果控制不好,数据就丢了
取元素也会阻塞:取元素时,队列里面无元素时,会阻塞非阻塞的方法,直接返回null )
阻塞方法
取queue的元素有两种:
1、读度列头部,如element、peek 2、读队列头部,并删除它,如remove、take、poll
add 满的时候 IllegalSlabExwceprion
remove 移除并返回队列头部的元素 如果队列为空则抛出一个异常NoSuchElmentException
element如果队列为空返回队列头部元素NoSuchElmentException异常
add、remove、element 脾气暴躁,动不动就抛异常

offer\poll\peek 是非阻塞方法。元素放不进去时,直接忽略;取不出来时,返回null
put\take 是阻塞方法。元素放不进去,或没元素可取时,线程挂起,知道放进去,或取到元素


ArrayBlockingQueue 从名称可判断:数据结构为Array、是阻塞的

private void enqueue(E x) {
// assert lock.getHoldCount() == 1;
// assert items[putIndex] == null;
final Object[] items = this.items;
items[putIndex] = x;
if (++putIndex == items.length) //putIndex 到头,putIndex=0
putIndex = 0;
count++;
notEmpty.signal();//唤醒等待中的元素
}
put 
public void put(E e) throws InterruptedException {
checkNotNull(e);
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
while (count == items.length)
notFull.await();//挂起 就是阻塞
enqueue(e);
} finally {
lock.unlock();
}
}

如何用数组实现一个queue :循环数组来实现 一个queue, 讲Conditon时讲过
什么是阻塞,condition 把线程挂起

问题:ArrayBlockingQueue 是否线程安全? 线程安全,都加锁了


LinkedBlocingQueue 阻塞 链表 线程安全的
自己回去看LInkedBlockingQuue的代码


ConcurrentLinkedQueue 在并发场景下使用, 我也是线程安全的
CAS 无锁编程
不blocking, 没有阻塞方法 take、put

CAS不能够保障性能绝对的好,可以保障我一个程序把资源占有的更多,单个程序性能一定更好

SychronousQueue 同步队里 很神奇
容量为0

put 没有容量,阻塞, 当有poll、take取元素时,结束阻塞
offer 元素直接丢了
如果有take 在阻塞中,offer也会成功

take 队列中无元素 =》 阻塞,当有put 、offer方法结束阻塞
poll 直接返回null
如果有一个put方法阻塞着,poll不返回null

peek只可能返回null,没有其他情况

PriorityBlockingQueue 通过传入comparator指定优先级规则

PriorityBlockingQueue<Student> queue =
    new PriorityBlockingQueue<>(5, new Comparator<Student>() {
        @Override
        public int compare(Student stu1, Student stu2) {
            if (stu1.age > stu2.age)
                return -1;
            else if (stu1.age == stu2.age)
                return 0;
            else
                return 1;
        }
    });

5、Semaphore

是一个技术信号量,常用于限制可以访问某些资源(物理或逻辑的)线程数目。 只能访问几个通道

简单说,是一种用来控制并发量的共享锁
在这里插入图片描述

本质是一个共享锁,用于限流
并发控制工具类与forkjoin
用法

本质是一个共享锁,用于限流

//创建信号量
 // static JamesSemaphore sp = new JamesSemaphore(5);
    static Semaphore sp = new Semaphore(5);

    public static void main(String args[]){
        //1000个线程都想去访问DB
        for (int i=0; i<1000; i++){
            new Thread(){
                @Override
                public void run() {
                    try {
                        sp.acquire();       //抢到访问通道的,才能够访问DB
                        Thread.sleep(2000L);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //queryDB("localhost:3006");
                    sp.release();       //访问完DB后,要释放通道
                }
            }.start();
        }
    }

模仿其源码机制实现Semaphore

//实现一个信号量
//共享锁,有上限
//AQS 技能提供共享锁 又能提供
//acquire release try…
//tryAcqirShared tryReleaseShared…

//不同点在于;有上限的共享锁
//当获取锁的个数,达到n个就不让在获取了,如果没有达到,据可以获取
//如果取锁的个数达到n个就不让在获取了,如果没有达到就可以获取
//如果已经到,就排队AQS
//只能够将值减1,你就是放成功了
//不管阻塞否,都要事先try方法

import java.util.concurrent.locks.AbstractQueuedSynchronizer;

public class JamesSemaphore {
    private Sync sync;

    public JamesSemaphore(int permits){
        this.sync = new Sync(permits);
    }


    public void acquire(){
        sync.tryAcquireShared(1);
    }

    public void release(){
        sync.tryReleaseShared(1);
    }

    class Sync extends AbstractQueuedSynchronizer {
        private int permits;

        public Sync(int permits){
            this.permits = permits;
        }


        @Override
        protected int tryAcquireShared(int arg) {
            int c = getState();
            int nextc = c + arg;

            if (nextc <= permits){
                if (compareAndSetState(c, nextc))
                    return 1;
            }
            return -1;
        }

        @Override
        protected boolean tryReleaseShared(int arg) {
            for(;;){
                int c = getState();
                if (c == 0) return false;

                int nextc = c - 1;
                if (compareAndSetState(c , nextc)){
                    return true;
                }
            }

        }
    }

}

CountDownLatch

这个类使一个线程等待其他线程各自执行完毕后再执行。
是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。或所有的线程在等待当一个线程在计数器值减1后执行程序。
创建 Test_CountDownLatch
用法

使用场景1:
    火箭起飞前,有很多检查需要做,每项检查需要的时间不同,
    完成全部10项检查后,火箭才能点火
CountDownLatch ctl = new CountDownLatch(10);
// 10个线程,分别做10 件事
for (int i=0; i<10; i++){
    int number = i;
    new Thread(){
        @Override
        public void run() {
            //模拟做任务所需要的时间
            int randomInt = new Random().nextInt(10);
            try {
                Thread.sleep(randomInt * 1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(number+ ">>>>" + "准备完毕");
            // 对外通知,我已经准备完毕
            ctl.countDown();
        }
    }.start();
}

//所有线程都昨晚了,才能点火
System.out.println("主线程开始等待。。。");
ctl.await();
System.out.println("点火...");

用法2

运动员在起跑线准备,等待口令,
裁判员:预备,跑!!!

//裁判员
JamesCountDownLatch ctl = new JamesCountDownLatch(1);

//6个运动员,即6个线程
for (int i=0; i<6; i++){
    int number = i;
    new Thread(){
        @Override
        public void run() {
            System.out.println(number + " is redy...");

            ctl.await();   //进入起跑线
            System.out.println(String.format("运动员%d起跑", number));

        }
    }.start();
}

System.out.println("预备");
Thread.sleep(3000);
ctl.countDown();        //鸣枪
System.out.println("跑!!!");

源码模仿实现:

public class JamesCountDownLatch {

    private Sync sync;

    public JamesCountDownLatch(int count){
        sync = new Sync(count);
    }

    public void countDown(){
        sync.releaseShared(1);
    }

    public void await(){
        sync.acquireShared(1);
    }


    /*
    CountDownLatch就可以看做是一个共享锁
    初始状态,这个共享锁被获取了n次,
    每次countdown,相当于释放一次锁
    当锁释放完后,其他线程才能再次获得锁

     */

    class Sync extends AbstractQueuedSynchronizer{
        public Sync(int count){
            setState(count);
        }

        /*
        在state=0时,获取锁成功,否则获取失败
         */
        @Override
        protected int tryAcquireShared(int arg) {
            return getState()==0 ? 1 : -1;
        }

        @Override
        protected boolean tryReleaseShared(int arg) {
            for (;;){
                int c = getState();
                if (c==0)
                    return false;
                int nextc = c-1;
                //用CAS操作尝试将state减一
                if (compareAndSetState(c, nextc)){
                    //当state变为0时,释放锁成功
                    return nextc == 0;
                }
            }
        }
    }

}

CyclicBarrier

从字面上的意思可以知道,这个类的中文意思是“循环栅栏”。大概的意思就是一个可循环利用的屏障。
它的作用就是会让所有线程都等待完成后才会继续下一步行动。
用法

//激流探险 验票员
JamesCyclicBarrier barrier  = new JamesCyclicBarrier(4);

//100个人在排队,用100个线程代表10个人
for (int i=0; i< 100; i++){
    new Thread(){
        @Override
        public void run() {
            //来了一个人后,不能马上上车,要等到4个人满了以后,一起出发
            barrier.await();
            System.out.println("出发。。。");
        }
    }.start();
    LockSupport.parkNanos(1000 * 1000 * 1000L);
}

源码

public class JamesCyclicBarrier {
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition condition = lock.newCondition();

    private int count = 0;
    private final int parties;
    private Object generation = new Object();



    public JamesCyclicBarrier(int parties) {
        if (parties <= 0)
            throw new IllegalArgumentException();
        this.parties = parties;
    }

    public void await() {
        final ReentrantLock lock = this.lock;
        lock.lock(); // 一个线程拿到锁
        try {
            final Object g = generation;

            int index = ++count;
            if (index == parties) {  // tripped 数量够了
                nextGeneration(); // 唤醒等待的线程继续执行。重新计数
                return;
            }

            //计数未到,进入等待
            //for循环,防止伪唤醒
            for (;;) {
                try {
                    condition.await();
                } catch (InterruptedException ie) {
                    //忽略
                }

                if (g != generation)     //generation不同,标记该线程不需要再等待
                    return;
            }
        } finally {
            lock.unlock();
        }
    }


    private void nextGeneration() {
        condition.signalAll();
        count = 0;
        generation = new Object();
    }

}

*CountDownLatch和CyclicBarrier区别:
1.countDownLatch是一个计数器,线程完成一个记录一个,计数器递减,只能只用一次
2.CyclicBarrier的计数器更像一个阀门,需要所有线程都到达,然后继续执行,计数器递增,提供reset功能,可以多次使用

2个线程的局部遍历无法共享只能时堆中的。

获取线程返回值

Runnable获取返回值

Runnable没有返回值,怎么获取返回值呢?

Thread th = new Thread(new Runnable() {
            @Override
            public void run() {

            }
        });
        th.start();

传入堆中的一个对象就行了

public class Result {
    private volatile Object result;


    public Object getResult() {
        return result;
    }

    public void setResult(Object result) {
        this.result = result;
    }
}

public static void main(String args[]) throws InterruptedException {
    Result result = new Result();

    Thread th = new Thread(new Runnable() {
        @Override
        public void run() {
            result.setResult("Hi James。。。。");
        }
    });
    th.start();

    Thread.sleep(3000);
    System.out.println(result.getResult());
}

Callable介绍

//创建Callable实现类
class CallableTask implements Callable<String>{
    @Override
    public String call() throws Exception {
        System.out.println(">>>执行任务。。。");

        //模拟耗时
        LockSupport.parkNanos(1000 * 1000 *1000 * 5L);
        return "success";
    }
}
public static void main(String args[]) throws InterruptedException, ExecutionException {
    //使用:用来包裹一个callab实例,得到的futureTask实例可以传入Thread()
    CallableTask task = new CallableTask();
    JamesFutureTask<String> future = new JamesFutureTask<>(task);

    new Thread(future).start();

    String result =  future.get();      //get方法会阻塞
    System.out.println(result);


    //一个futureTask实例,只能使用一次
    //同时说明,这个任务,从头到尾只会被一个线程执行
    new Thread(future).start();

}

Callable和Runnable明显能看到区别:
1.Callable能接受一个泛型,然后在call方法中返回一个这个类型的值。而Runnable的run方法没有返回值
2.Callable的call方法可以抛出异常,而Runnable的run方法不会抛出异常。

FutureTask

一个可取消的异步计算。FutureTask提供了对Future的基本实现,可以调用方法去开始和取消一个计算,可以查询计算是否完成并且获取计算结果。只有当计算完成时才能获取到计算结果,一旦计算完成,计算将不能被重启或者被取消,除非调用runAndReset方法。一个futureTask实例,只能使用一次。
模仿源码实现

public class JamesFutureTask<T> implements Runnable{

    public JamesFutureTask(Callable<T> call){
        this.call = call;
    }

    private Callable<T> call;

    T result;


    //Runner,用来实现抢执行的权限
    AtomicReference<Thread> runner = new AtomicReference<>();

    //等待队列
    LinkedBlockingQueue<Thread> waiters = new LinkedBlockingQueue<>();

    //任务状态
    private volatile int state = NEW;

    private static final int NEW = 0;
    private static final int RUNNING = 1;
    private static final int FINISHED = 2;
    @Override
    public void run() {
        if (state != NEW ||
                !runner.compareAndSet(null, Thread.currentThread())){
            return;
        }

        state = RUNNING;
        try {
            result = call.call();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            state = FINISHED;
        }

        while (true){
            Thread th = waiters.poll();
            if (th == null){
                break;
            }
            LockSupport.unpark(th);
        }
    }

    public T get(){
        if (state != FINISHED){
            waiters.offer(Thread.currentThread());
        }

        //挂起线程
        while (state!=FINISHED){
            LockSupport.park();
        }

        return result;
    }
}

ForkJoin

使用ForkJoin最核心的内容,就是定义 递归任务,定义递归任务,即定义如何对Task进行拆分,对结果进行汇总
定义就放在compute方法中,披着线程池外衣的任务拆分、结果汇总框架

    public static void main(String args[]) throws ExecutionException, InterruptedException {
        Job job = new Job(urls, 0, urls.size());
        ForkJoinTask<String> forkJoinTask =  forkJoinPool.submit(job);

        String result = forkJoinTask.get();
        System.out.println(result);
    }

//使用ForkJoin最核心的内容,就是定义 递归任务,
    //定义递归任务,即定义如何对Task进行拆分,对结果进行汇总
    //定义就放在compute方法中
    static class Job extends RecursiveTask<String>{

        List<String> urls;
        int start;
        int end;

        public Job(List<String> urls, int start, int end){
            this.urls = urls;
            this.start = start;
            this.end = end;
        }


        @Override
        protected String compute() {
            int count = end - start;        //计算任务大小

            //若任务比较小,就直接执行,  // 10
            if (count <=10){
                String result = "";
                for (int i = start; i< end; i++){
                    String response = doRequest(urls.get(i));
                    result += response;
                }
                return result;
            }else{
                //否则,拆分任务
                int x = (start + end) / 2;
                Job job1 = new Job(urls, start, x);
                job1.fork();
                
                Job job2 = new Job(urls, x, end);
                job2.fork();
                
                //汇总结果
                String result = "";
                result += job1.join();
                result +=job2.join();
                return result;
            }
        }
    }

注解:本章的内容的代码可参考:https://github.com/fanxishu/java2020peixun/tree/master/subject-1


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值