并发编程学习笔记(二)——JDK并发包

版权声明:本文为博主原创,未经博主允许不得转载。 https://blog.csdn.net/weixin_36904568/article/details/90347276

一:同步控制

1. synchronized的扩展——重入锁

同一个线程可以反复进入的锁,比synchronized同步块具有更大的灵活性。

  • 创建锁
    • 非公平: ReentrantLock lock = new ReentrantLock();
    • 公平: ReentrantLock lock = new ReentrantLock(true);
  • 使用锁:lock.lock();
    • 使用可中断锁:lock.lockInterruptibly();
    • 使用限时锁:lock.tryLock(时间, TimeUnit单位);
  • 释放锁:lock.unlock();
    • 判断是否拥有锁:lock.isHeldByCurrentThread()

(1)处理同步问题

对临界资源加锁,进行互斥同步

import java.util.concurrent.locks.ReentrantLock;

public class ReenterLockTest implements Runnable{
    public static ReentrantLock lock = new ReentrantLock();

    public static int i;

    @Override
    public void run() {
        for (int j = 0; j < 10; j++) {
            lock.lock();
            try {
                i++;
            }
            finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReenterLockTest test = new ReenterLockTest();
        Thread a = new Thread(test);
        Thread b = new Thread(test);
        a.start();
        b.start();
        a.join();
        b.join();
        System.out.println(i);
    }
}

(2)中断响应

在等待锁的时候,被等待线程可以通知其中断等待

 public void run() {
        for (int j = 0; j < 10; j++) {
            try {
                lock.lockInterruptibly();
                i++;
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println("被通知不用等待了");
            }
            finally {
                if (lock.isHeldByCurrentThread())
                     lock.unlock();
                System.out.println("线程退出");
            }
        }
    }

(3)锁申请等待限时

  • 有参数:线程等待一段时间后若锁不可用,直接放弃
  • 没有参数:直接判断是否可用,不可用直接放弃
public void run() {
        for (int j = 0; j < 10; j++) {
            if (lock.tryLock()) {
                //业务代码
                try {
                    i++;
                } finally {
                    if (lock.isHeldByCurrentThread())
                        lock.unlock();
                    System.out.println("线程退出");
                }
            }
        }
    }

(4)公平锁

默认非公平锁,线程倾向于再次获取已经持有的锁,高效
公平锁,每个线程都有机会获得锁,开销大

2. 重入锁的伙伴——条件

让锁在特定的时间等待和通知

  • 创建:Condition condition = lock.newCondition()
  • 等待
    • await():获取特定的锁后,使线程等待,同时释放锁,当线程被中断也能跳出等待
    • awaitUninterruptibly():线程中等待中无法响应中断
  • 通知
    • signal():获取特定的锁后,唤醒等待的线程,释放锁
    • signalAll():获取特定的锁后,唤醒所有等待的线程,释放锁
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class ReenterLockTest implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    public static Condition condition = lock.newCondition();
    public static int i;

    @Override
    public void run() {
        if (lock.tryLock()) {
            //业务代码
            try {
                i++;
                System.out.println("开始等待");
                condition.await();
                System.out.println("被唤醒了");
            } catch (InterruptedException e) {
                System.out.println("我在等待时被中断了");
            } finally {
                if (lock.isHeldByCurrentThread())
                    lock.unlock();
                System.out.println("线程退出");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReenterLockTest test = new ReenterLockTest();
        Thread a = new Thread(test);
        a.start();
        //等待线程A把锁释放,否则拿不到锁
        Thread.sleep(2000);
        lock.lock();
        condition.signal();
        lock.unlock();
        System.out.println(i);
    }
}

3. 锁的扩展——信号量Semaphore

允许多个资源同时访问同个临界资源

  • 创建:
    • 指定同时访问的线程个数:Semaphore semaphore = new Semaphore(线程数)
    • 指定线程个数和公平锁:Semaphore semaphore = new Semaphore(线程数,true)
  • 进入临界区:
    • semaphore.acquire():获取进入临界区的许可,获取失败则等待或者被中断
    • semaphore.acquireUninterruptibly():尝试获取进入临界区的许可,获取失败则等待,无法响应中断
    • semaphore.tryAcquire():尝试获取进入临界区的许可,获取失败直接退出
  • 退出临界区:
    • semaphore.release():访问资源结束后释放许可
import java.util.concurrent.Semaphore;

public class SemaphoreTest {

    static Semaphore semaphore = new Semaphore(5);

    static class MyThread extends Thread{

        @Override
        public void run() {
            try {
                semaphore.acquire();
                Thread.sleep(2000);
                System.out.println(Thread.currentThread().getId()+"正在执行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            finally {
                semaphore.release();
            }
        }
    }
    public static void main(String[] args) {
        MyThread[] threads = new MyThread[10];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new MyThread();
            threads[i].start();
        }
    }
}

4. 读写锁—— ReadWriteLock

读写分离锁可以有效帮助减少锁竞争,提升系统性能。
允许多个线程同时读,不会造成阻塞,但是读写和写写间的操作依然需要等待和持有锁。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLock {
    private static ReentrantLock myLock = new ReentrantLock();
    private static ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private static Lock readLock = lock.readLock();
    private static Lock writeLock = lock.writeLock();

    static class ReadThread extends Thread{
        @Override
        public void run() {
//            myLock.lock();
            readLock.lock();
            System.out.println("开始大量读操作");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            finally {
//                myLock.unlock();
                readLock.unlock();
            }
        }
    }
    static class WriteThread extends Thread{
        @Override
        public void run() {
            try {
//                myLock.lock();
                writeLock.lock();
                System.out.println("开始写操作");
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                writeLock.unlock();
//                myLock.unlock();
            }

        }
    }

    public static void main(String[] args) {
        ReadThread[] readThreads = new ReadThread[10];
        for (int i = 0; i < readThreads.length; i++) {
            readThreads[i] = new ReadThread();
            readThreads[i].start();
        }
        WriteThread[] writeThreads = new WriteThread[2];
        for (int i = 0; i < writeThreads.length; i++) {
            writeThreads[i] = new WriteThread();
            writeThreads[i].start();
        }
    }
}

5. 倒计数器——CountDownLatch

让线程等待直到倒计数结束后再执行,每当有一个线程完成工作时计数-1,当所有线程完成工作计数为0,则等待的线程可以继续执行

import java.util.concurrent.CountDownLatch;

public class CountDownLatchTest {
    private static CountDownLatch latch = new CountDownLatch(2);
    static class CheckThreadA extends Thread{
        @Override
        public void run() {
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程A检查完毕");
            latch.countDown();
        }
    } static class CheckThreadB extends Thread{
        @Override
        public void run() {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程B检查完毕");
            latch.countDown();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        CheckThreadA a = new CheckThreadA();
        CheckThreadB b = new CheckThreadB();
        a.start();
        b.start();
        //等待所有线程完成工作后,主线程才能继续进行
        latch.await();
        System.out.println("都检查好了");
    }
}

6. 循环栅栏——CyclicBarrier

可以重复使用的倒计数器

import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class CyclicBarrierTest {
    //主线程需要完成的所有任务
    static class BarrierThread extends Thread{

        private int flag;

        public BarrierThread(int flag) {
            this.flag = flag;
        }

        @Override
        public void run() {
            switch (flag){
                case 0:
                    System.out.println("第一个任务完成!");
                    flag = 1;
                    break;
                case 1:
                    System.out.println("第二个任务完成!");
                    flag = 2;
                    break;
                default:
                    System.out.println("任务失败!");
                    break;
            }
        }
    }
    
    //子线程需要分别完成的任务
    static class MyThread extends Thread{

        private String name;

        private CyclicBarrier barrier;

        public MyThread(CyclicBarrier barrier,String name) {
            this.barrier = barrier;
            this.name = name;
        }

        @Override
        public void run() {
            try {
                System.out.println(name+"开始第一个工作");
                Thread.sleep(Math.abs(new Random().nextInt()%10000));
                //等待其他线程一起完成第一个工作
                barrier.await();
                System.out.println(name+"开始第二个工作");
                Thread.sleep(Math.abs(new Random().nextInt()%10000));
                //等待其他线程一起完成第二个工作
                barrier.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (BrokenBarrierException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        int N = 10;
        CyclicBarrier barrier = new CyclicBarrier(N,new BarrierThread(0));
        MyThread[] threads = new MyThread[N];
        for (int i = 0; i < N; i++) {
            threads[i] = new MyThread(barrier,"线程"+i);
            threads[i].start();
        }
    }
}

7. 线程阻塞工具类——LockSupport

可以实现在任意位置让线程阻塞,并且不会和resume一样让线程突然终止,也不需要向wait一样先获取对象的锁,也不会产生中断异常(出现中断默默退出)

  • 阻塞:lock.park(),lock.parkNanos(时间),lock.parkUntil(时间)
    • 如果临界区资源可用,则立即返回并将其变为不可用(只有一个可以进入)
    • 如果临界区资源不可用,则阻塞
  • 唤醒:lock.unpark()
    • 将临界区资源变为可用

8. 限流工具类——RateLimiter

  • 漏桶算法:利用一个存储区,当有请求进入系统时,无论请求的速率如何,先保存在存储区中,再以固定的速率流出存储区处理
  • 令牌桶算法:在每个单位时间内产生一定量的令牌存入桶中,程序只有拿到令牌才能处理请求,否则丢弃请求(避免系统崩溃)或等待令牌

RateLimiter实现了令牌桶算法

  • 创建:RateLimiter limiter = RateLimiter.create(令牌数)
  • 限流
    • 等待令牌:limiter.acquire();
    • 丢弃请求:limiter.tryAcquire();
import com.google.common.util.concurrent.RateLimiter;

public class RateLimiterTest {
    private static RateLimiter limiter = RateLimiter.create(2);
    static volatile int i = 0;
    static class MyThread extends Thread{
        @Override
        public void run() {
            System.out.println(System.currentTimeMillis());
        }
    }

    public static void main(String[] args) {
        MyThread[] threads = new MyThread[10];
        for (int j = 0; j < threads.length; j++) {
//            limiter.acquire();
            if (!limiter.tryAcquire())
                continue;
            threads[i] = new MyThread();
            threads[i].start();
        }

    }
}

二:线程池

1. 基本概念

(1)原因

大量的线程会耗尽CPU和内存资源,可能会使创建和销毁线程所占用的时间超过线程真实工作的时间,大量的线程回收也会延长GC的停顿时间

(2)线程池

在线程池中维护多个活跃线程,在用户需要线程时,由原来的创建线程变成了从线程池获取空闲线程;在用户工作完成后,由原来的销毁线程变成了归还线程给线程池

(3)分类

  • 普通线程服务
    • FixedThreadPool:维护固定数量的线程,在任务提交时,如果有空闲线程则执行任务,否则把任务存放在任务队列中
    • SingleThreadExecutor:维护一个线程,当有多个任务提交时,把其他任务存放在任务队列中
    • CachedThreadPool:维护动态变化数量的线程,在任务提交时,如果有空闲线程则直接执行任务,否则创建新线程执行任务,之后新线程返回线程池复用
  • 调度线程服务
    • SingleThreadScheduledExecutor:维护一个线程,可以延迟执行任务或周期性执行任务
    • ScheduledThreadPool:维护多个线程,可以延迟执行任务或周期性执行任务

(4)使用线程服务

从线程工厂Executors获取所需要的线程服务ExecutorService或者调度线程服务ScheduledExecutorService

1. 普通线程服务
  • submit:提交指定的任务去执行并且返回Future对象,即执行的结果
  • execute:提交指定的任务去执行,打印异常信息
  • shutdown:拒绝接受新提交的任务,但是会继续运行之前的任务
  • shutdownNow:拒绝接受新提交的任务,同时停止正在运行的任务
  • invokeAll:批量完成多个任务,在所有任务完成后返回
  • invokeAny:批量完成多个任务,在一个任务完成后返回
import java.util.concurrent.*;

public class ThreadPoolTest {

    static class Task implements Runnable {

        @Override
        public void run() {
            System.out.println("当前线程ID为:" + Thread.currentThread().getId());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("我在睡眠时被中断了");
            }
        }
    }


    public static void main(String[] args) {
        Task task = new Task();
        ExecutorService service = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 10; i++) {
            service.submit(task);
        }
//        service.shutdown();
        service.shutdownNow();
        try {
            Thread.sleep(2000);
            service.submit(task);
        } catch (RejectedExecutionException exception) {
            System.out.println("已经拒绝提交任务了");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2. 调度线程服务
  • schedule:延迟一段时间后执行任务
  • scheduleAtFixRate:上一次任务的开始时间之后过一段时间调度下一次任务(如果任务执行时间大于调度时间,则在任务执行后开始下一次任务)
  • scheduleWithFixDelay:上一次任务的结束时间之后过一段时间调度下一次任务(如果任务执行时间大于调度时间,则在任务执行后调度一段时间开始下一次任务)

如果任务出现异常,则后续任务无法继续执行

import java.util.concurrent.*;

public class ThreadPoolTest {

    static class Task implements Runnable {

        @Override
        public void run() {
            System.out.println("当前线程ID为:" + Thread.currentThread().getId());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("我在睡眠时被中断了");
            }
        }
    }


    public static void main(String[] args) {
        Task task = new Task();
        ScheduledExecutorService service = Executors.newScheduledThreadPool(10);
//        service.scheduleAtFixedRate(task,0,2, TimeUnit.SECONDS);
        service.scheduleWithFixedDelay(task,0,2,TimeUnit.SECONDS);
    }
}

2. 线程池的底层实现

(1)基本线程池

线程池都是由ThreadPoolExecutor的构造函数实现的:

/**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters and default thread factory and rejected execution handler.
     * It may be more convenient to use one of the {@link Executors} factory
     * methods instead of this general purpose constructor.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }

可以看到,线程池主要参数如下:

  • corePoolSize:线程数量
  • maximumPoolSize:最大线程数量
  • keepAliveTime:空闲线程存活时间
  • unit:时间单位
  • workQueue:任务队列
    • 直接提交队列(SynchronousQueue):没有容量的队列,入队即出队,总是会将提交的任务交给线程执行(创建新线程),如果线程数量达到最大值则采取拒绝策略
    • 有界任务队列(ArrayBlockingQueue):固定容量的队列,在线程数<corePoolSize时创建新线程,否则将任务加入队列。在任务队列满后且线程数<maximumPoolSize时创建新线程,否则采取拒绝策略
    • 无界任务队列(LinkedBlockingQueue):无限容量的队列,不存在任务队列满的情况,可能会耗尽系统内存。在线程数<corePoolSize时创建新线程,否则将任务加入队列。
    • 优先任务队列(PriorityBlockingQueue):特殊无界队列,可以控制任务顺序
  • defaultThreadFactory:默认线程工厂,创建线程
  • defaultHandler:默认拒绝策略
    • AbortPolicy:直接抛出异常
    • CallerRunsPolicy:直接在调用者线程中运行被丢弃的任务(性能下降)
    • DiscardPolicy:默默丢弃任务
    • DiscardOldestPolicy:丢弃即将执行的任务,尝试再次提交任务

(2)具体线程池

newFixedThreadPool:

线程个数和最大线程个数相同,采用无界任务队列

public static ExecutorService newFixedThreadPool(int nThreads) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>());
    }

newSingleThreadExecutor:

退化的newFixedThreadPool

public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

newCachedThreadPool:

最大线程个数为无穷大,采用直接提交任务队列

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }

(3)线程池的执行

/**
     * Executes the given task sometime in the future.  The task
     * may execute in a new thread or in an existing pooled thread.
     *
     * If the task cannot be submitted for execution, either because this
     * executor has been shutdown or because its capacity has been reached,
     * the task is handled by the current {@code RejectedExecutionHandler}.
     *
     * @param command the task to execute
     * @throws RejectedExecutionException at discretion of
     *         {@code RejectedExecutionHandler}, if the task
     *         cannot be accepted for execution
     * @throws NullPointerException if {@code command} is null
     */
    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

直接调度执行任务:

if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
  }

加入任务队列:

if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
 }

拒绝策略:

 else if (!addWorker(command, false))
            reject(command);

(4)自定义线程池、线程工厂、拒绝策略

import java.util.concurrent.*;

public class MyThreadPoolTest {

    static class Task implements Runnable {

        @Override
        public void run() {
            String group = Thread.currentThread().getThreadGroup().getName();
            String name = Thread.currentThread().getName();
            System.out.println(group+"--"+name);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                System.out.println("我在睡眠时被中断了");
            }
        }
    }

    //初始化一个固定容量的,使用合适的无界任务队列容量的,采用自定义工厂和自定义拒绝策略的线程池
    static class MyThreadPool extends ThreadPoolExecutor{

        public MyThreadPool(int size) {
            super(size, size, 0L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(10),new MyThreadFactory(),new MyRejectHandler());
        }
        
        @Override
        protected void beforeExecute(Thread t, Runnable r) {
            System.out.println("准备执行任务:"+r.toString());
    }

        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            System.out.println("任务执行完毕:"+r.toString());
        }

        @Override
        protected void terminated() {
            System.out.println("线程池退出");
        }
    }

    //自定义线程工厂
    static class MyThreadFactory implements ThreadFactory{

        int index = 0;

        ThreadGroup group;

        @Override
        public Thread newThread(Runnable r) {
            //记录创建时间
            System.out.println("正在创建线程---"+System.currentTimeMillis());
            //自定义线程名称,组名,优先级
            group = new ThreadGroup("线程组A");
            Thread thread = new Thread(group,r);
            thread.setName("线程-"+index++);
            thread.setPriority(Thread.NORM_PRIORITY);
            //设置守护线程
            thread.setDaemon(false);
            return thread;
        }
    }

    //自定义拒绝策略
    static class MyRejectHandler implements RejectedExecutionHandler{

        @Override
        public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
            System.out.println("我拒绝了一个任务"+r.toString());
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Task task = new Task();
        ExecutorService service = new MyThreadPool(5);
        for (int i = 0; i < 10; i++) {
            service.submit(task);
            Thread.sleep(100);
        }
        service.shutdown();
    }
}

处理异常
		@Override
        public void execute(Runnable command) {
            super.execute(wrap(command,clientTrace()));
        }

        @Override
        public Future<?> submit(Runnable task) {
            return super.submit(wrap(task,clientTrace()));
        }

        //提交任务的堆栈信息
        private Exception clientTrace(){
            return new Exception("clientTrace");
        }

        //在捕捉到异常时,打印提交任务时的异常,再打印任务本身的异常
        private Runnable wrap(final Runnable task,final Exception trace){
            return new Runnable() {
                @Override
                public void run() {
                    try {
                        task.run();
                    }
                    catch (Exception e){
                        trace.printStackTrace();
                        throw  e;
                    }
                }
            };
        }

3. ForkJoin线程池

一个大任务通过fork方法开启多个分支线程,处理小任务,小任务处理完成后使用join方法等待其他分支线程完成,最终得到结果。

空闲线程会帮助其他线程,在其任务队列尾端取出任务执行
如果线程一直空闲,则会被挂起并压入栈,待有新的任务时唤醒并出栈

  • RecursiveAction:没有返回值的任务
  • RecursiveTask:携带返回值的任务
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;

public class ForkJoinTest {

    static class Task extends RecursiveTask<Long> {

        private long start;

        private long end;

        public Task(Long start, Long end) {
            this.start = start;
            this.end = end;
        }

        @Override
        protected Long compute() {
            long sum = 0;
            long step = (start + end) / 100;    //分解
            ArrayList<Task> tasks = new ArrayList<>();  //记录子任务
            long index = start;
            //拆分成多个子任务并提交
            for (int i = 0; i < 100; i++) {
                Task task = new Task(index, index+step > end ? end : index+step);
                index = step + 1;
                tasks.add(task);
                task.fork();
            }
            //执行子任务
            for (Task t :
                    tasks) {
                sum += t.join();
            }

            return sum;
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ForkJoinPool pool = new ForkJoinPool();
        Task task = new Task(0L, 200000L);
        //提交任务
        ForkJoinTask<Long> result = pool.submit(task);
        //获取返回值
        long res = result.get();
        System.out.println("结果:" + res);
    }
}

4. Guava支持的线程池

  • DirectExecutor:将任务在当前线程中直接执行,统一同步调用和异步调用
  • Daemon线程池:通过MoreExecutors.getExitingExecutorService(线程服务),使得线程服务对应的连接池转为当前线程的守护线程池

三:并发容器

1. 线程安全的HashMap

(1)Collections包装

Map map = Collections.synchronizedMap(new HashMap<>());
  • 简单
  • 多线程环境下性能差

(2)ConcurrentHashMap

ConcurrentHashMap map = new ConcurrentHashMap();

在内部进一步细分若干个小的HashMap,默认16段。在加锁时根据哈希值获取对应的哈希段并加锁

  • 适合高并发

(3)跳表SkipList

ConcurrentSkipListMap map = new ConcurrentSkipListMap();

用于快速查找的数据结构,本质上维护一个分层、有序的链表

修改表只需要局部修改,通过随机算法插入
查找表时从上层到下层跳跃式查询

组成:

保存键值对的节点

static final class Node<K,V> {
        final K key;
        volatile Object value;
        volatile Node<K,V> next;

        /**
         * Creates a new regular node.
         */
        Node(K key, Object value, Node<K,V> next) {
            this.key = key;
            this.value = value;
            this.next = next;
        }
  }

节点通过CAS操作设置键值对,指向下一个结点

		boolean casValue(Object cmp, Object val) {
            return UNSAFE.compareAndSwapObject(this, valueOffset, cmp, val);
        }

        /**
         * compareAndSet next field
         */
        boolean casNext(Node<K,V> cmp, Node<K,V> val) {
            return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
        }

保存节点的索引

static class Index<K,V> {
        final Node<K,V> node;
        final Index<K,V> down;
        volatile Index<K,V> right;

        /**
         * Creates index node with given values.
         */
        Index(Node<K,V> node, Index<K,V> down, Index<K,V> right) {
            this.node = node;
            this.down = down;
            this.right = right;
        }
}

通过CAS操作,指向右边的索引

        /**
         * compareAndSet right field
         */
        final boolean casRight(Index<K,V> cmp, Index<K,V> val) {
            return UNSAFE.compareAndSwapObject(this, rightOffset, cmp, val);
        }

头索引

static final class HeadIndex<K,V> extends Index<K,V> {
        final int level;
        HeadIndex(Node<K,V> node, Index<K,V> down, Index<K,V> right, int level) {
            super(node, down, right);
            this.level = level;
        }
    }

2. 线程安全的List

(1)Vector

Vector vector = new Vector();
  • 性能差

(2)Collections包装

List list = Collections.synchronizedList(new ArrayList<>());
  • 多线程环境下性能差

(3)ConcurrentLinkedQueue

ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();

通过使用CAS设置值、下一结点、头尾结点

	boolean casItem(E cmp, E val) {
            return UNSAFE.compareAndSwapObject(this, itemOffset, cmp, val);
        }
	boolean casNext(Node<E> cmp, Node<E> val) {
            return UNSAFE.compareAndSwapObject(this, nextOffset, cmp, val);
        }
    private boolean casTail(Node<E> cmp, Node<E> val) {
        return UNSAFE.compareAndSwapObject(this, tailOffset, cmp, val);
    }

   	private boolean casHead(Node<E> cmp, Node<E> val) {
        return UNSAFE.compareAndSwapObject(this, headOffset, cmp, val);
    }
  • 只有一个结点:通过循环保证插入新结点成功
  • 多个结点:通过循环查找到最后一个结点并插入,更新尾结点
  • 哨兵结点:需要重新循环查找到最后一个结点或者是未结点
/**
     * Inserts the specified element at the tail of this queue.
     * As the queue is unbounded, this method will never return {@code false}.
     *
     * @return {@code true} (as specified by {@link Queue#offer})
     * @throws NullPointerException if the specified element is null
     */
    public boolean offer(E e) {
        checkNotNull(e);
        final Node<E> newNode = new Node<E>(e);

        for (Node<E> t = tail, p = t;;) {
            Node<E> q = p.next;
            if (q == null) {
                // p is last node
                if (p.casNext(null, newNode)) {
                    // Successful CAS is the linearization point
                    // for e to become an element of this queue,
                    // and for newNode to become "live".
                    if (p != t) // hop two nodes at a time
                        casTail(t, newNode);  // Failure is OK.
                    return true;
                }
                // Lost CAS race to another thread; re-read next
            }
            else if (p == q)
                // We have fallen off list.  If tail is unchanged, it
                // will also be off-list, in which case we need to
                // jump to head, from which all live nodes are always
                // reachable.  Else the new tail is a better bet.
                p = (t != (t = tail)) ? t : head;
            else
                // Check for tail updates after two hops.
                p = (p != t && t != (t = tail)) ? t : q;
        }
    }

(4) 高效读取

CopyOnWriteArrayList:通过锁实现并发,性能较高

CopyOnWriteArrayList list = new CopyOnWriteArrayList();
  • 只阻塞写和写的操作
    • 读操作之间:不会出现并发
    • 写操作和读操作之间:对数据的副本加锁,进行写操作,再替换原来的数据并解锁。由于数据使用volatile修饰,具有可见性。

CopyOnWriteLinkedQueue:通过CAS操作实现并发

(5)数据共享通道BlockingQueue

实现线程之间的数据共享

  • ArrayBlockingQueue:有界
  • LinkedBlockingQueue:无界

入队:

  • offer:如果队列为满返回false
  • put:如果队列为满则等待

出队:

  • poll:如果队列为空返回null
  • take:如果队列为空则等待

实现:通过可重入锁和Condition条件变量进行入队和出队的条件控制(生产者-消费者模型)

猜你喜欢

转载自blog.csdn.net/weixin_36904568/article/details/90347276