JUC相关

JUC

提供并发编程的解决方案

CAS是java.util.concurrent.atomic包的基础

AQS是java.util.concurrent.locks包以及一些常用类比如Semophore,ReentrantLock等类的基础

 

使用node实现FIFO队列,可以用于构建锁或者其他的同步装置的基础框架

利用了一个类型表示状态

使用方法为继承

子类通过继承并通过实现他的方法管理其状态(acquire和release)的方法操纵状态

可以同时实现排他锁和共享锁模式(独占、共享)

aqs维护一个CLH队列来管理锁,线程会首先尝试获取锁,如果失败,则将当前线程与等待状态等信息变为一个node节点加入到sync queue,接着不断循环尝试获取锁,当前结点为head的直接后继才会尝试,如果失败,则会阻塞自己直到被唤醒,当持有锁的线程释放锁的时会唤醒队列中的后继线程

分类

线程执行器executor

锁locks

原子变量类atomiic

并发工具类tools

并发集合collections

线程池(Executors)

 

种类说明
newFixThreadPool(int nThreads)指定工作线程数量的线程池
newCachedThreadPool()

处理大量短时间工作任务的线程池

(1)试图缓存线程并重用,当无缓存线程可用时,就会创建新的工作线程

(2)如果线程闲置的时间超过阈值,则会被终止并移出线程池

(3)系统长时间闲置时候,不会消耗什么资源

newSingleThreadExecutor()创建唯一的工作线程来执行任务,如果异常结束,就用另一个线程替换

newSingleThreadScheduledExecutor()

newSchduledExecutor(int n)

完成定时或周期性的工作调度
newWorkStrealingPool()内部构建ForkJoinPool,work-strealing算法,并行处理任务,不保证顺序处理

fork/join框架1.7引入

把大任务分割成若干个小任务并行执行,最终汇总每个小任务结果得到大任务结果的框架

work-stealing:某个线程从其他队列里窃取任务来执行

降低资源消耗、提高线程的可管理性

简单例子:

public class ForkJoinTaskExample extends RecursiveTask<Integer> {
    public static final int threshold = 2;
    private int start;
    private int end;

    public ForkJoinTaskExample(int start, int end) {
        this.start = start;
        this.end = end;
    }

    @Override
    protected Integer compute() {
        int sum = 0;
        boolean canCompute = (end - start) <= threshold;
        if (canCompute) {
            for (int i = start; i < end; i++) {
                sum += i;
            }

        } else {
            //拆分为两个子任务计算
            int middle = (start + end) / 2;
            ForkJoinTaskExample leftTask = new ForkJoinTaskExample(start, middle);
            ForkJoinTaskExample rightTask = new ForkJoinTaskExample(middle + 1, end);
            //执行子任务
            leftTask.fork();
            rightTask.fork();
            //等待任务执行结束并合并结果
            int leftResult = leftTask.join();
            int rightResult = rightTask.join();
            //合并子任务
            sum = leftResult + rightResult;
        }
        return sum;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        ForkJoinTaskExample task = new ForkJoinTaskExample(1, 100);
        Future<Integer> result = forkJoinPool.submit(task);
        System.out.println(result.get());
    }
}

Executor框架示意图

 

 

构造函数如下,对应上图进行一个说明,比如newFixedThreaPool,他其实是一个ThreadPoolExecutor类,该类继承AbstractExecutorService类,该类实现了ExecutorService接口,而ExecutorService接口又继承Executor接口

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}
     public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
  public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

public static ExecutorService newWorkStealingPool() {
        return new ForkJoinPool
            (Runtime.getRuntime().availableProcessors(),
             ForkJoinPool.defaultForkJoinWorkerThreadFactory,
             null, true);
    }
   public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
        return new DelegatedScheduledExecutorService
            (new ScheduledThreadPoolExecutor(1));
    }

JUC三个Executor接口

Executor:运行新任务的简单接口,将任务提交和任务细节解耦,只定义了void execute(Runnable command)这个方法

ExecutorService:具备管理执行器和任务生命周期的方法,任务机制更为完善

SchduledExecutorService:支持Future和定期执行任务

ThreadPoolExecutor结构

workQueue负责接收任务,workThread则是处理从workqueue里获取的实现runnable接口的任务,ThreadFactory则负责管理工作线程,并且也有相应的拒绝任务的机制

主要组成部分

corePoolSize:核心线程数

maximumPoolSize:线程不够用时能够创建的最大线程数

workQueue:任务等待队列

keepAliveTime:当线程池的线程数量大于corePoolsize时,超过keepAliveTime后,其他的线程会被销毁,仅保证核心线程数

threadFactory:创建新线程,Executors.defaultThreadFactory(),代码如下

static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final ThreadGroup group;
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            SecurityManager s = System.getSecurityManager();
            group = (s != null) ? s.getThreadGroup() :
                                  Thread.currentThread().getThreadGroup();
            namePrefix = "pool-" +
                          poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(group, r,
                                  namePrefix + threadNumber.getAndIncrement(),
                                  0);
            if (t.isDaemon())
                t.setDaemon(false);
            if (t.getPriority() != Thread.NORM_PRIORITY)
                t.setPriority(Thread.NORM_PRIORITY);
            return t;
        }
    }
 

handler:线程池饱和策略

AbortPolicy:直接抛出异常,默认策略

CallerRunsPolicy:用调用者所在线程来执行任务

DiscardOldestPolicy:丢弃队列中最靠前的任务,并执行当前任务

DiscardPolicy:直接丢弃任务

实现RejectedExecutionHandler接口的自定义handler

 

新任务提交execute执行后的判断

如果运行的线程少于corePoolSize,则创建新线程来处理任务,即使线程池中的其他线程是空闲的;

如果线程池中的线程数量大于等于corePoolSize且小于maximumPoolSize,则只有当workQueue满时才创建新的线程去处理任务

如果设置的corePoolSize和maximumPoolSize相同,则创建的线程池的大小是固定的,这时如果有新任务提交,若workQueue未满,则将请求放入workQueue中,等待有空闲的线程去从workQueue中取出任务并处理

如果运行线程数量大于等于maximumPoolSize,如果工作队列满了,则通过handler所指定的策略来处理任务

线程池状态

running:能接受新提交任务,并且也能处理阻塞队列中的任务

shutdown:不再接受新提交任务,但可以处理存量任务

stop:不再接受新提交的任务,也不处理存量任务

tidying:所有任务已经终止

terminted:terminated()方法执行后进入改状态

工作线程的生命周期

线程池的大小选定

cpu密集型:线程数=按照核数或核数+1设定

i/o密集型:线程数=cpu核数*(1+平均等待时间/平均工作时间)

new Thread的弊端:

新建对象,性能差

线程缺乏统一的管理,可能无限制新建线程,相互竞争,有可能占用过多的线程资源导致死机或OOM

缺乏更多的功能,如更多执行,顶期执行,线程中断

线程池好处:

重用存在的线程,减少对象创建、消亡的开销,性能佳

可有效控制最大并发线程数,提高系统资源利用率,同时避免过多资源竞争,避免阻塞

提供定时执行、定期执行、单线程、并发数控制等功能

 

locks

引入显式锁,方便对线程共享资源做更细粒度的锁控制

Condition对象由lock对象创建,并且可以创建多个,lock和condition都是通过aqs实现的,aqs通过lockSupport类的unpark,park来实现线程的唤醒、阻塞。condition可以理解为条件队列,即同一个锁可以根据不同条件来进行线程唤醒和阻塞,而不像synchorized那样,不同条件也会阻塞

可指定公平锁还是非公平锁

提供Condition,可分组唤醒需要唤醒的线程

提供可以中断等待锁的线程机制,lock.lockInterruptibly()

代码例子

package com.company.lock;


import java.util.ArrayDeque;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @author lulu
 * @Date 2019/7/17 20:25
 */
public class TestLock {
    //定义一个锁
    final Lock lock = new ReentrantLock();

    //代表需要生产的情况
    final Condition gen = lock.newCondition();
    //代表需要消费的情况
    final Condition take = lock.newCondition();
    private ArrayDeque arrayDeque = new ArrayDeque();
    private volatile static Boolean stop = false;

    public static void main(String args[]) throws InterruptedException {
        TestLock lock = new TestLock();
        TestLock.Producer p = lock.new Producer();
        TestLock.Producer p1 = lock.new Producer();
        TestLock.Customer c = lock.new Customer();
        TestLock.Customer c1 = lock.new Customer();
        p.start();
        p1.start();
        c.start();
        c1.start();
        TimeUnit.MILLISECONDS.sleep(10);
        stop = true;
    }

    class Customer extends Thread {
        @Override
        public void run() {
            while (!stop) {
                lock.lock();//加入aqs的sync 队列
                while (arrayDeque.isEmpty()) {
                    System.out.println("队列空,等待生产");
                    try {
                        take.await();//移出sync队列,此时加入take的condition等待队列,对应释放锁,需要信号才可以释放

                    } catch (InterruptedException e) {
                    }
                }
                System.out.println(Thread.currentThread().getName() + "消费一个元素");
                arrayDeque.poll();
                gen.signalAll();//唤醒在等待的生产condition队列去获取锁
                lock.unlock();
            }

        }
    }

    class Producer extends Thread {

        @Override
        public void run() {
            while (!stop) {
                lock.lock();//由于其他线程释放锁,当前线程加入aqs的sync队列
                while (arrayDeque.size() == 5) {
                    System.out.println("队列满,等待消费");
                    try {
                        gen.await();//相当于Object.wait方法,直到gen signal选中才继续执行

                    } catch (InterruptedException e) {
                    }
                }
                System.out.println(Thread.currentThread().getName() + "加入一个元素");
                arrayDeque.offer("");
                take.signalAll();//发送信号,如果condition里面有等待的线程节点,则加入sync对列,但没有被唤醒
                lock.unlock();//释放锁,按从头到尾唤醒sync队列的线程
            }

        }
    }
}

ReentranReadWriteLock(支持读读操作,适用于读多于写的并发场景)

atomic

各种操作的原子性,具有原子操作的类,使用CAS更新。详情点这里

tools(并发工具类)

闭锁CountDownLatch:

让一个主线程等待一组事件发生后继续执行

事件是指CountDownLatch的countDown()方法,举个不是很切合的例子,中转站快递投递通知,一个用户买了三个商品,需要都到达某个中转战时,相应的快递通知才会被发送,商品1到达后,发送给用户已经到达中转站,然后继续运输,当用户收到三个商品都到中转站通知时,通知就可以关闭了

package com.p_thread.collections;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

/**
 * @author lulu
 * @Date 2019/7/17 22:10
 */
public class CountDownLatchDemo {

    public static void main(String[] args) throws InterruptedException {
        test();
    }

    private static void test() throws InterruptedException {
        CountDownLatch c=new CountDownLatch(3);
        IntStream.rangeClosed(1,3).mapToObj(i->new Task(c,i)).forEach(e->e.start());
        c.await();
        System.out.println("所有线程到达,开始执行主线程逻辑");
    }
    static class Task extends Thread{
        private CountDownLatch c;
       public Task(CountDownLatch c,int i){
           super(i+"");
           this.c=c;
       }

        @Override
        public void run() {
            try {
                TimeUnit.SECONDS.sleep((long)Math.random()*5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+"到达该点");
            c.countDown();
        }
    }
}

栅栏CyclicBarrier:

阻塞当前线程,等待其他线程

1.等待其他线程,且会阻塞自己当前线程,所有线程必须同时到达栅栏的位置后,才能继续执行

2.所有线程到达栅栏处,可以触发执行另一个预先设置的线程

以刚刚快递投递信息发送的通知为例子,所有商品到达中转站后,只能停留,等所有到达后,快递通知才会被发送,而后再进行运输

代码例子,该代码把单线程和多线程做一个比较,在数据量较少时,单线程执行效率高,当数据量大的时候,多线程优势才展现出来,这里要把jvm参数-Xmx -Xms调高一点

package com.p_thread.collections;

import java.util.Arrays;
import java.util.concurrent.*;

/**
 * @author lulu
 * @Date 2019/5/18 0:21
 */
public class CyclicBarrierDemo {
   /* public static void test() throws ExecutionException, InterruptedException {
        FutureTask t=new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {

                TimeUnit.SECONDS.sleep(5);
                return "ok";
            }
        });

        new Thread(t).start();
        System.out.println("线程开启");
        System.out.println(t.get());

        ExecutorService s=Executors.newSingleThreadExecutor();
       Future f= s.submit(new Callable() {
            @Override
            public Object call() throws Exception {

                TimeUnit.SECONDS.sleep(5);
                return "ok";
            }
        });
        System.out.println(f.get());
s.shutdown();

    }*/


    public static void main(String args[]){
    /*    try {
            test();
        } catch (ExecutionException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }*/
        int size = 400000000;
        //定义数组
        int[] numbers = new int[size];
        //随机初始化数组
        for (int i = 0; i < size; i++) {
            numbers[i] = (int)(Math.random()*10);
        }

        //单线程计算结果
        System.out.println();
        long start = System.currentTimeMillis();
        Long sum = 0L;
        for (int i = 0; i < size; i++) {
            sum += numbers[i];
        }
        long end=System.currentTimeMillis();
        System.out.println("单线程计算结果:" + sum+"耗时:"+(end-start)+"");

        //多线程计算结果
        //定义线程池
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        //定义5个Future去保存子数组计算结果
        final int[] results = new int[5];

        //定义一个循环屏障,在屏障线程中进行计算结果合并
        long start1 = System.currentTimeMillis();
        CyclicBarrier barrier = new CyclicBarrier(5, () -> {
            int sums = 0;
            for (int i = 0; i < 5; i++) {
                sums += results[i];
            }
            long end1=System.currentTimeMillis();
            System.out.println("多线程计算结果:" + sums+"耗时:"+(end1-start1)+"");
        });

        //子数组长度
        int length = 80000000;
        //定义5个线程去计算
        for (int i = 0; i < 5; i++) {
            //定义子数组
            int[] subNumbers = Arrays.copyOfRange(numbers, (i * length), ((i + 1) * length));
            //计算结果
            int finalI = i;
            executorService.submit(() -> {
                for (int j = 0; j < subNumbers.length; j++) {
                    results[finalI] += subNumbers[j];
                }
                //等待其他线程进行计算
                try {
                    barrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            });
        }

        //关闭线程池
        executorService.shutdown();
    }
}
      

信号量Semaphore:

控制某个资源可被同时访问的线程个数

例子:运行结果实先出现那五个线程,然后再出现后面2个线程的打印信息

package com.p_thread.collections;

import java.util.Random;
import java.util.concurrent.*;

/**
 * @author lulu
 * @Date 2019/7/17 22:28
 */
public class SemaphoreDemo {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService e = Executors.newCachedThreadPool();
        CountDownLatch countDownLatch = new CountDownLatch(7);
        final Semaphore semp = new Semaphore(5);
        long start = System.currentTimeMillis();
        for (int i = 0; i < 7; i++) {
            e.submit(new Task(semp, countDownLatch));
        }
        countDownLatch.await();
        long end = System.currentTimeMillis();
        System.out.println(end - start);
        e.shutdown();

    }


    static class Task implements Runnable {
        private Semaphore semaphore;
        private CountDownLatch countDownLatch;

        public Task(Semaphore semaphore, CountDownLatch countDownLatch) {
            this.semaphore = semaphore;
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            try {
                semaphore.acquire();
                long s = (long) (new Random().nextInt(5) + 1);
                System.out.println(Thread.currentThread().getName() + "执行" + s + "秒");
                TimeUnit.SECONDS.sleep(s);
                semaphore.release();
                countDownLatch.countDown();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

交换机Exchanger:

两个线程到达同步点后,相互交换数据,下图为Thread1和Thread2,并非都是Thread1

例子:

package com.p_thread.collections;

import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

/**
 * @author lulu
 * @Date 2019/7/17 22:53
 */
public class ExchangerDemo {
   static class Task {
       public Task(int math,int english){
           this.math=math;
           this.english=english;
       }
        private int  math;
        private int english;
    }
    public static void main(String[] args) {
        final Exchanger exchanger=new Exchanger();
        ExecutorService service= Executors.newFixedThreadPool(2);
        service.execute(()->{
            Object a= null ;
            try {
                a = exchanger.exchange(null);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Task b=(Task)a;
            System.out.println("线程1汇报:处理完成,总分"+(b.english+b.math));
            Object c= null;
            try {
                c = exchanger.exchange("我完成了");
                System.out.println("线程1收到线程2消息--"+c);

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        service.execute(()->{
            try {
                System.out.println("线程2:我准备阻塞了,帮我做一下");
                Object b=exchanger.exchange(new Task(99,100));
                TimeUnit.SECONDS.sleep(3);
                Object x1=exchanger.exchange("线程2收到了");
                System.out.println("线程2收到线程1消息--"+x1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        service.shutdown();
    }
}

collections

concurrentHashMap

不允许插入null键,利用CAS和synchronized实现线程安全,比起以前的分段锁锁粒度更新

插入逻辑:

1.判断Node[]数组(bucket)是否初始化,没有则进行初始化操作

2.通过hash定位数组的索引坐标,是否有node节点,没有则进行CAS添加(链表的头节点),添加失败则进入下次循环

3.检查到内部正在扩容则帮助扩容

4.如果头节点不为空,则使用synchronized锁住头节点

5.判断链表长度是否到达8,如果到达的话则把该链表树化

blockingQueue:阻塞队列,队列满入队、队列空出队均会阻塞

throws Exception如果不能马上执行,抛出一个异常

special value如果不能马上执行,返回一个特殊值(true或false)

blocks不能马上执行阻塞

timesout超时即返回特殊值(一般为true或false)

ArrayBlockQueue先进先出,有界

DelayQueue,阻塞内部元素,里元素要实现delayed接口,该接口继承comparable,需要进行排序,一般对元素的过期时间的优先级进行排序,定时关闭链接,缓存对象,超时处理等等,内部实现ReentranLock+PriorityQueue

LinkedBlockingQueue:链表实现,其他和ArrayBlockQueue一致

PriorityBlockingQueue:无边界,允许插入空对象,元素实现comparable接口,优先队列

SynchronousQueue:容量为一的队列

 

copyonwrite:读写分离,最终一致性,使用时开辟新空间操作解决冲突,写操作加锁。

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值