aoe1231

知之为知之,不知为不知

Java并发编程学习笔记7——共享模型之工具

1、线程池

1.1、自定义线程池

@Slf4j
public class TestPool {
    public static void main(String[] args) {
        ThreadPool threadPool = new ThreadPool(1, 1000, TimeUnit.MILLISECONDS, 1, (queue, task) -> {
            // 1、死等
//            queue.put(task);
            // 2、带超时等待
//            queue.offer(task, 500, TimeUnit.MILLISECONDS);
            // 3、让调用者放弃任务执行
//            log.debug("放弃:{}", task);
            // 4、让调用者抛出异常
//            throw new RuntimeException("任务执行失败" + task);
            // 5、让调用者自己执行任务
            task.run();

        });
        for (int i = 0; i < 3; i++) {
            int j = i;
            threadPool.execute(() -> {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("{}", j);
            });
        }
    }
}

@FunctionalInterface // 拒绝策略
interface RejectPolicy<T> {
    void reject(BlockingQueue<T> queue, T task);
}

@Slf4j
@Data
class ThreadPool {
    // 任务队列
    private BlockingQueue<Runnable> taskQueue;

    // 线程集合
    private HashSet<Worker> workers = new HashSet<>();

    // 核心线程数
    private int coreSize;

    // 获取任务的超时时间
    private long timeout;

    // 时间单位
    private TimeUnit timeUnit;

    // 拒绝策略
    private RejectPolicy<Runnable> rejectPolicy;

    public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit, int queueCapacity, RejectPolicy<Runnable> rejectPolicy) {
        this.coreSize = coreSize;
        this.timeout = timeout;
        this.timeUnit = timeUnit;
        this.taskQueue = new BlockingQueue<>(queueCapacity);
        this.rejectPolicy = rejectPolicy;
    }

    // 执行任务
    public void execute(Runnable task) {
        // 当任务数没有超过 coreSize 时,直接就交给Worker对象执行
        // 如果任务数超过了coreSize时,加入任务队列暂存
        synchronized (workers) {
            if (workers.size() < coreSize) {
                Worker worker = new Worker(task);
                log.debug("新增 worker:{},任务:{}", worker, task);
                workers.add(worker);
                worker.start();
            } else {
                // 1) 死等
//                taskQueue.put(task);
                // 2) 带超时的等待
                // 3) 让调用者放弃任务执行
                // 4) 让调用者抛出异常
                // 5) 让调用者自己执行任务
                taskQueue.tryPut(rejectPolicy, task);
            }
        }
    }

    // 包装线程为Worker类
    class Worker extends Thread {
        private Runnable task;

        public Worker(Runnable task) {
            this.task = task;
        }

        @Override
        public void run() {
            // 执行任务
            // 1) 当 task 不为null,执行任务
            // 2) 当 task 执行完毕,再接着从任务队列获取任务并执行
            while (task != null || (task = taskQueue.poll(timeout, timeUnit)) != null) {
                try {
                    log.debug("正在执行...{}", task);
                    task.run();
                } catch (Exception e) {
                } finally {
                    task = null;
                }
            }
            synchronized (workers) {
                log.debug("worker被移除:{}", this);
                workers.remove(this);
            }
        }
    }
}

// 阻塞队列
@Slf4j
@Data
class BlockingQueue<T> {
    // 1、任务队列
    private Deque<T> queue = new ArrayDeque<>();
    // 2、锁
    private ReentrantLock lock = new ReentrantLock();
    // 3、生产者条件变量
    private Condition fullWaitSet = lock.newCondition();
    // 4、消费者条件变量
    private Condition emptyWaitSet = lock.newCondition();
    // 5、容量
    private int capacity;

    public BlockingQueue(int capacity) {
        this.capacity = capacity;
    }

    // 带超时的阻塞获取
    public T poll(long timeout, TimeUnit unit) {
        T t = null;
        lock.lock();
        try {
            // 将 timeout 统一转换为纳秒
            long nanos = unit.toNanos(timeout);
            while (queue.isEmpty()) {
                if (nanos <= 0) {
                    return null;
                }
                // 返回的是剩余时间,如果剩余时间小于等于0,说明超时了
                nanos = emptyWaitSet.awaitNanos(nanos);
            }
            t = queue.removeFirst();
            fullWaitSet.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return t;
    }

    // 阻塞获取
    public T take() {
        T t = null;
        lock.lock();
        try {
            while (queue.isEmpty()) {
                emptyWaitSet.await();
            }
            t = queue.removeFirst();
            fullWaitSet.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return t;
    }

    // 阻塞添加
    public void put(T task) {
        lock.lock();
        try {
            while (queue.size() == capacity) {
                log.debug("等待加入任务队列...{}", task);
                fullWaitSet.await();
            }
            log.debug("加入任务队列:{}", task);
            queue.addLast(task);
            emptyWaitSet.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    // 带超时时间的阻塞添加
    public boolean offer(T task, long timout, TimeUnit timeUnit) {
        lock.lock();
        try {
            long nanos = timeUnit.toNanos(timout);
            while (queue.size() == capacity) {
                log.debug("等待加入任务队列...{}", task);
                if (nanos < 0) {
                    return false;
                }
                nanos = fullWaitSet.awaitNanos(nanos);
            }
            log.debug("加入任务队列:{}", task);
            queue.addLast(task);
            emptyWaitSet.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
        return true;
    }

    // 指定拒绝策略的添加方法
    public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
        lock.lock();
        try {
            // 判断队列是否已满
            if (queue.size() == capacity) {
                rejectPolicy.reject(this, task);
            } else {
                // 有空闲
                log.debug("加入任务队列:{}", task);
                queue.addLast(task);
                emptyWaitSet.signal();
            }
        } finally {
            lock.unlock();
        }
    }

    // 获取大小
    public int size() {
        lock.lock();
        try {
            return queue.size();
        } finally {
            lock.unlock();
        }
    }
}

结果:
14:47:07.418 [main] DEBUG com.multiThreads.Test18.ThreadPool - 新增 worker:Thread[Thread-0,5,main],任务:com.multiThreads.Test18.TestPool$$Lambda$2/1365202186@26f0a63f
14:47:07.422 [main] DEBUG com.multiThreads.Test18.BlockingQueue - 加入任务队列:com.multiThreads.Test18.TestPool$$Lambda$2/1365202186@7d6f77cc
14:47:07.423 [Thread-0] DEBUG com.multiThreads.Test18.ThreadPool - 正在执行...com.multiThreads.Test18.TestPool$$Lambda$2/1365202186@26f0a63f
14:47:08.424 [main] DEBUG com.multiThreads.Test18.TestPool - 2
14:47:08.424 [Thread-0] DEBUG com.multiThreads.Test18.TestPool - 0
14:47:08.424 [Thread-0] DEBUG com.multiThreads.Test18.ThreadPool - 正在执行...com.multiThreads.Test18.TestPool$$Lambda$2/1365202186@7d6f77cc
14:47:09.424 [Thread-0] DEBUG com.multiThreads.Test18.TestPool - 1
14:47:10.425 [Thread-0] DEBUG com.multiThreads.Test18.ThreadPool - worker被移除:Thread[Thread-0,5,main]

1.2、ThreadPoolExecutor

1.2.1、线程池状态

ThreadPoolExecutor使用int的高3位表示线程池状态,低29位表示线程数量。

状态明 高3位 接收新任务 处理阻塞队列任务 说明
RUNNING 111 Y Y  
SHUTDOWN 000 N Y 不会接收新任务,但会处理阻塞队列剩余任务
STOP 001 N N 会中断正在执行的任务,并抛弃阻塞队列任务
TIDYING 010 - - 任务全部执行完毕,活动线程为0即将进入终结
TERMINATED 011 - - 终结状态

从数字上比较,TERMINATED > TIDYING > STOP > SHUTDOWN > RUNNING。

这些信息存储在一个原子变量ctl中,目的是将线程池状态与线程个数合二为一,这样就可以用一次CAS原子操作进行赋值。

    // c为旧值, ctlOf()返回结果为新值
    ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c)));
        
    // rs 为高3位代表线程池状态,wc 为低29位代表线程个数,ctl是合并它们
    private static int ctlOf(int rs, int wc) {
        return rs | wc;
    }

1.2.2、构造方法

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

corePoolSize: 核心线程数目(最多保留的线程数);
maximumPoolSize: 最大线程数目;
keepAliveTime: 生存时间 - 针对救急线程;
unit: 时间单位 - 针对救急线程;
workQueue: 阻塞队列;
threadFactory: 线程工厂 - 可以为线程创建时起个好名字;
handler: 拒绝策略

工作方式:

  • 线程池中刚开始没有线程,当一个任务提交给线程池后,线程池会创建一个新线程来执行任务;
  • 当线程数达到corePoolSize并没有线程空闲,这时再加入任务,新加的任务会被加入workQueue队列排队,直到有空闲的线程;
  • 如果队列选择了有界队列,那么任务超过了队列大小时,会创建maximumPoolSize - corePoolSize数目的线程来救急;
  • 如果线程到达maximumPoolSize仍然有新任务这时会执行拒绝策略,拒绝策略jdk提供了4种实现,其它著名框架也提供了实现:① AbortPolicy让调用者抛出RejectExecutionException异常,这时默认策略;② CallerRunsPolicy让调用者运行任务;③ DiscardPolicy放弃队列中最早的任务,本任务取而代之;④ Dubbo的实现,在抛出RejectExecutionException异常之前会记录日志,并dump线程栈信息,方便定位问题;⑤ Netty的实现,是创建一个新线程来执行任务;⑥ ActiveMQ的实现,带超时等待(60s)尝试放入队列,类似我们之前定义的拒绝策略;⑦ PinPoint的实现,它使用了一个拒绝策略链,会逐一尝试策略链中每种拒绝策略。
  • 当高峰过去后,超过corePoolSize的救急线程如果一段时间没有任务做,需要结束节省资源,这个时间由keepAliveTime和unit来控制。

根据这个构造方法,JDK Executors类中提供了众多工厂方法来创建各种用途的线程池。

1.2.3、newFixedThreadPool

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

特点:

  • 核心线程数 == 最大线程数(没有救急线程被创建),因此也无需超时时间;
  • 阻塞队列是无界的,可以放任意数量的任务。

评价:适用于任务量已知,相对耗时的任务。

1.2.4、newCachedThreadPool

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

特点:

  • 核心线程数是0,最大线程数是Integer.MAX_VALUE,救急线程的空闲生存时间是60s,意味着:① 全部都是救急线程(60s后可以回收);② 救急线程可以无限创建。
  • 队列采用了SynchronizedQueue实现特点是,它没有容量,没有线程来取是放不进去的(一手交钱,一手交货)。
@Slf4j
public class Test1 {
    public static void main(String[] args) throws InterruptedException {
        SynchronousQueue<Integer> integers = new SynchronousQueue<>();
        new Thread(()->{
            try {
                log.debug("putting {}", 1);
                integers.put(1);
                log.debug("{} putted...", 1);

                log.debug("putting...{}", 2);
                integers.put(2);
                log.debug("{} putted...", 2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t1").start();

        Thread.sleep(1000);

        new Thread(()->{
            try {
                log.debug("taking {}", 1);
                integers.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t2").start();

        Thread.sleep(1000);

        new Thread(()->{
            try {
                log.debug("taking {}", 2);
                integers.take();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "t3").start();
    }
}

结果:
16:01:43.540 [t1] DEBUG com.multiThreads.Test18.Test1 - putting 1
16:01:44.539 [t2] DEBUG com.multiThreads.Test18.Test1 - taking 1
16:01:44.540 [t1] DEBUG com.multiThreads.Test18.Test1 - 1 putted...
16:01:44.540 [t1] DEBUG com.multiThreads.Test18.Test1 - putting...2
16:01:45.540 [t3] DEBUG com.multiThreads.Test18.Test1 - taking 2
16:01:45.540 [t1] DEBUG com.multiThreads.Test18.Test1 - 2 putted...

评价:整个线程池表现为线程数会根据任务量不断增长,没有上限,当任务执行完毕,空闲1分钟后释放线程。适合任务数比较密集,但每个任务执行时间较短的情况。

1.2.5、newSingleThreadExecutor

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

使用场景:希望多个任务排队执行,线程数固定为1,任务数多于1时,会放入无界队列排队。任务执行完毕,这唯一的线程也不会被释放。

区别:

  • 自己创建一个单线程串行执行任务,如果任务执行失败而而终止那么没有任何不久措施,而线程池还会新建一个线程,保证池的正常工作;
  • Executors.newSingleThreadExecutor()线程个数始终为1,不能修改:FinalizableDelegatedExecutorService应用的是装饰器模式,只对外暴露了ExecutorService接口,因此不能调用ThreadPoolExecutor中特有的方法;
  • Executors.newFixedThreadPool(1)初始时为1,以后还可以修改:对外暴露的是ThreadPoolExecutor对象,可以强转后调用setCorePoolSize等方法进行修改。

1.2.6、提交任务

// 执行任务
void execute(Runnable command);

// 提交任务 task,用返回值 Future 获得任务执行结果
<T> Future<T> submit(Callable<T> task);

// 提交 tasks 中所有任务
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks) throws InterruptedException;

// 提交 tasks 中所有任务,带超时时间
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException;

// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其他任务取消
<T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExcutionException;

// 提交 tasks 中所有任务,哪个任务先成功执行完毕,返回此任务执行结果,其他任务取消,带超时时间
<T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
@Slf4j
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(2);

        Future<String> future = pool.submit(new Callable<String>() {
            @Override
            public String call() throws Exception {
                log.debug("running");
                Thread.sleep(1000);
                return "ok";
            }
        });

        log.debug("{}", future.get());
    }
}

结果:
10:32:54.309 [pool-1-thread-1] DEBUG demo.Test - running
10:32:55.315 [main] DEBUG demo.Test - ok
@Slf4j
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(2);

        List<Future<String>> futures = pool.invokeAll(Arrays.asList(
                () -> {
                    log.debug("begin");
                    Thread.sleep(1000);
                    return "1";
                },
                () -> {
                    log.debug("begin");
                    Thread.sleep(500);
                    return "2";
                },
                () -> {
                    log.debug("begin");
                    Thread.sleep(2000);
                    return "3";
                }
        ));

        futures.forEach(f -> {
            try {
                log.debug("{}", f.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });
    }
}

结果:
10:41:38.026 [pool-1-thread-2] DEBUG demo.Test - begin
10:41:38.026 [pool-1-thread-1] DEBUG demo.Test - begin
10:41:38.536 [pool-1-thread-2] DEBUG demo.Test - begin
10:41:40.537 [main] DEBUG demo.Test - 1
10:41:40.538 [main] DEBUG demo.Test - 2
10:41:40.538 [main] DEBUG demo.Test - 3
@Slf4j
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(1);

        // 谁先执行完,结果就是谁(返回最先得到的结果)
        String result = pool.invokeAny(Arrays.asList(
                () -> {
                    log.debug("begin 1");
                    Thread.sleep(1000);
                    log.debug("end 1");
                    return "1";
                },
                () -> {
                    log.debug("begin 2");
                    Thread.sleep(500);
                    log.debug("end 2");
                    return "2";
                },
                () -> {
                    log.debug("begin 3");
                    Thread.sleep(2000);
                    log.debug("end 3");
                    return "3";
                }
        ));
        log.debug("{}", result);
    }
}

结果:
10:48:18.370 [pool-1-thread-1] DEBUG demo.Test - begin 1
10:48:19.375 [pool-1-thread-1] DEBUG demo.Test - end 1
10:48:19.375 [pool-1-thread-1] DEBUG demo.Test - begin 2
10:48:19.375 [main] DEBUG demo.Test - 1

1.2.7、关闭线程池

1、shutdown()

/*
线程池状态变为SHUTDOWN
    - 不会接收新任务
    - 但已提交的任务会执行完
    - 此方法不会阻塞调用线程的执行
*/
void shutdown();

- 源代码:
    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            // 修改线程池状态
            advanceRunState(SHUTDOWN);
            // 仅会打断空闲线程
            interruptIdleWorkers();
            // 扩展点 ScheduledThreadPoolExecutor
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        // 尝试终结(没有运行的线程可以立刻终结,如果还有运行的线程也不会等)
        tryTerminate();
    }

2、shutdownNow()

/*
线程池状态变为 STOP
    - 不会接收新任务
    - 会将队列中的任务返回
    - 并用interrupt() 的方式中断正在执行的任务
*/

- 源代码:
    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            // 修改线程池状态
            advanceRunState(STOP);
            // 打断所有线程
            interruptWorkers();
            // 获取队列中剩余任务
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        // 尝试终结
        tryTerminate();
        return tasks;
    }

3、其他方法

// 不在 RUNNING 状态的线程池,此方法就返回true
boolean isShutdown();

// 线程池状态是否是 TERMINATED
boolean isTerminated();

// 调用 shutdown() 后,由于调用线程并不会等待所有任务运行结束,因此如果它想在线程池 TERMINATED 后做些事情,可以利用此方法等待。
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
@Slf4j
public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(1);

        Future<?> result1 = pool.submit(() -> {
            log.debug("task 1 running...");
            Thread.sleep(1000);
            log.debug("task 1 finish...");
            return 1;
        });

        Future<?> result2 = pool.submit(() -> {
            log.debug("task 2 running...");
            Thread.sleep(1000);
            log.debug("task 2 finish...");
            return 2;
        });

        Future<?> result3 = pool.submit(() -> {
            log.debug("task 3 running...");
            Thread.sleep(1000);
            log.debug("task 3 finish...");
            return 3;
        });

        log.debug("shutdown");
//        pool.shutdown();
//        // 设置等待时间,等待终止(所有任务都执行完)
//        pool.awaitTermination(3, TimeUnit.SECONDS);
        List<Runnable> runnables = pool.shutdownNow();
        log.debug("other...{}", runnables);
    }
}

结果:
11:17:20.090 [main] DEBUG demo.Test - shutdown
11:17:20.090 [pool-1-thread-1] DEBUG demo.Test - task 1 running...
11:17:20.092 [main] DEBUG demo.Test - other...[java.util.concurrent.FutureTask@442d9b6e, java.util.concurrent.FutureTask@ee7d9f1]

1.2.8、任务调度线程池

在【任务调度线程池】功能加入之前,可以使用java.util.Timer来实现定时功能,Timer的优点在于简单易用,但由于所有任务都是由同一个线程来调度,因此所有任务都是串行执行的,同一时间只能有一个任务在执行,前一个任务的延迟或异常都将会影响到之后的任务。

@Slf4j
public class Test2 {
    public static void main(String[] args) {
        Timer timer = new Timer();
        TimerTask task1 = new TimerTask() {
            @SneakyThrows
            @Override
            public void run() {
                log.debug("task 1");
                Thread.sleep(2000);
            }
        };
        TimerTask task2 = new TimerTask() {
            @Override
            public void run() {
                log.debug("task 2");
            }
        };
        // 使用 timer 添加两个任务,虚妄它们都在 1s 后执行
        // 但由于 timer 内只有一个线程来顺序执行队列中的任务,因此【任务1】的延时,影响了【任务2】的执行
        log.debug("start...");
        timer.schedule(task1, 1000);
        timer.schedule(task2, 1000);
    }
}

结果:
14:42:11.769 [main] DEBUG demo.Test2 - start...
14:42:12.778 [Timer-0] DEBUG demo.Test2 - task 1
14:42:14.784 [Timer-0] DEBUG demo.Test2 - task 2
@Slf4j
public class Test2 {
    public static void main(String[] args) {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);

        pool.schedule(()->{
            log.debug("task 1 start");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("task 1 end");
        }, 1, TimeUnit.SECONDS);

        pool.schedule(()->{
            log.debug("task 2 start");
            log.debug("task 2 end");
        }, 1, TimeUnit.SECONDS);

    }
}

结果:
14:48:24.431 [pool-1-thread-1] DEBUG demo.Test2 - task 1 start
14:48:26.444 [pool-1-thread-1] DEBUG demo.Test2 - task 1 end
14:48:26.444 [pool-1-thread-1] DEBUG demo.Test2 - task 2 start
14:48:26.444 [pool-1-thread-1] DEBUG demo.Test2 - task 2 end
@Slf4j
public class Test2 {
    public static void main(String[] args) {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);

        // 下次任务的延时从上次任务的开始时算
//        log.debug("start...");
//        pool.scheduleAtFixedRate(()->{
//            log.debug("running");
//            try {
//                Thread.sleep(2000);
//            } catch (InterruptedException e) {
//                e.printStackTrace();
//            }
//        }, 1, 1, TimeUnit.SECONDS);

        // 下次任务的延时从上次任务的结束时算
        pool.scheduleWithFixedDelay(()->{
            log.debug("running");
            try {
                // 如果前面的任务时间过长,则会等待前一次任务执行完
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },1,1,TimeUnit.SECONDS);
    }
}

结果:
14:54:44.972 [pool-1-thread-1] DEBUG demo.Test2 - running
14:54:47.982 [pool-1-thread-1] DEBUG demo.Test2 - running
14:54:51.000 [pool-1-thread-1] DEBUG demo.Test2 - running
14:54:54.011 [pool-1-thread-1] DEBUG demo.Test2 - running
...
@Slf4j
public class Test2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService pool = Executors.newFixedThreadPool(1);
        Future<Boolean> f = pool.submit(() -> {
            log.debug("task 1");
            int i = 1 / 0;
            return true;
        });
        log.debug("result:{}", f.get());
    }
}

结果:
15:02:28.889 [pool-1-thread-1] DEBUG demo.Test2 - task 1
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
	at demo.Test2.main(Test2.java:19)
Caused by: java.lang.ArithmeticException: / by zero
	at demo.Test2.lambda$main$0(Test2.java:16)
	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
	at java.lang.Thread.run(Thread.java:750)
@Slf4j
public class Test2 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 如何让每周四 18:00:00 定时执行任务?
        // 获取当前时间
        LocalDateTime now = LocalDateTime.now();
        // 获取周四时间
        LocalDateTime time = now.withHour(18).withMinute(0).withSecond(0).withNano(0).with(DayOfWeek.THURSDAY);
        // 如果 当前时间 > 本周周四 ,必须找到下周周四
        if (now.compareTo(time) > 0) {
            time = time.plusWeeks(1); // 增加一周
        }
        // initialDelay 代表当前时间和周四的时间差
        long initialDelay = Duration.between(now, time).toMillis();
        // period : 一周的间隔时间
        long period = 1000 * 60 * 60 * 24 * 7;

        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);
        pool.scheduleAtFixedRate(()->{
            System.out.println("running...");
        }, initialDelay, period, TimeUnit.MILLISECONDS);
    }
}

1.2.9、Tomcat线程池

Tomcat在哪里用到了线程池呢?

  • LimitLatch用来限流,可以控制最大连接个数,类似J.U.C中的Semaphore后面再讲;
  • Acceptor只负责【接收新的socket连接】;
  • Poller只负责监听socket channel是否有【可读的I/O事件】;
  • 一旦可读,封装一个任务对象(socketProcessor),提交给Executor线程池处理;
  • Executor线程池中的工作线程最终负责【处理请求】。

Tomcat线程池扩展了ThreadPoolExecutor,行为稍有不同:

  • 如果总线程数达到maximumPoolSize:① 这时不会立刻抛RejectedExecutionException异常;② 而是再次尝试将任务放入队列,如果还失败,才抛出RejectedExecutionException异常。
Connector配置
配置项 默认值 说明
acceptorThreadCount 1 acceptor线程数量
pollerThreadCount 1 poller线程数量
minSpareThreads 10 核心线程数
maxThreads 200 最大线程数,即maximumPoolSize
executor - Executor名称,用来引用下面的Executor
Executor线程配置
配置项 默认值 说明
threadPriority 5 线程优先级
daemon true 是否守护线程
minSpareThreads 25 核心线程数,即corePoolSize
maxThreads 200 最大线程数,即maximumPoolSize
maxIdleTime 60000 线程生存时间,单位是毫秒,默认值即1分钟
maxQueueSize Integer.Max_VALUE 队列长度
prestartminSpareThreads false 核心线程是否在服务器启动时启动

1.3、Fork / Join

1.3.1、概念

Fork / Join 是JDK 1.7加入的新的线程池实现,它体现的是一种分治思想,适用于能够进行任务拆分的CPU密集型运算。

所谓的任务拆分,是将一个大任务拆分为算法上相同的小任务,直至不能拆分可以直接求解。跟递归相关的一些计算,比如归并排序、斐波那契数列,都可以用分治思想进行求解。

Fork / Join在分治的基础上加入了多线程,可以把每个任务的分解和合并交给不同的线程来完成,进一步提升了运算效率。

Fork / Join默认会创建与CPU核心数大小相同的线程池。

1.3.2、使用

提交给Fork / Join线程池的任务需要继承RecursiveTask(有返回值)或RecursiveAction(没有返回值),例如下面定义了一个对1~n之间的整数求和的任务。

public class Test3 {
    public static void main(String[] args) {
        ForkJoinPool pool = new ForkJoinPool(4);
        Integer result1 = pool.invoke(new MyTask(4));
        System.out.println(result1 + "\n");
        Integer result2 = pool.invoke(new MyTaskPlus(1,4));
        System.out.println(result2 + "\n");
    }
}

// 1~n之间整数的和
@Slf4j(topic = "c.MyTask")
class MyTask extends RecursiveTask<Integer> {
    private int n;
    public MyTask(int n) {
        this.n = n;
    }

    @Override
    public String toString() {
        return "{" + n + "}";
    }

    @Override
    protected Integer compute() {
        // 终止条件
        if (n == 1) {
            log.debug("join() {}", n);
            return n;
        }
        MyTask t1 = new MyTask(n - 1);
        t1.fork(); // 让一个线程去执行此任务
        log.debug("fork() {} + {}", n, t1);

        int result = n + t1.join(); // 获取任务结果
        log.debug("join() {} + {} = {}", n, t1, result);
        return result;
    }
}

// 1~n之间整数的和
@Slf4j(topic = "c.MyTaskPlus")
class MyTaskPlus extends RecursiveTask<Integer> {
    // 1 ~ 5
    private int begin;
    private int end;
    public MyTaskPlus(int begin, int end) {
        this.begin = begin;
        this.end = end;
    }

    @Override
    public String toString() {
        return "{" + begin + "," + end + "}";
    }

    @Override
    protected Integer compute() {
        // 终止条件
        if (begin == end) {
            log.debug("join() {}", begin);
            return begin;
        }
        if (end - begin == 1) {
            log.debug("join() {} + {} = {}", begin, end, end + begin);
            return end + begin;
        }

        int mid = (end + begin) / 2;

        MyTaskPlus t1 = new MyTaskPlus(begin, mid);
        t1.fork();
        MyTaskPlus t2 = new MyTaskPlus(mid + 1, end);
        t2.fork();
        log.debug("fork() {} + {} = ?", t1, t2);

        int result = t1.join() + t2.join();
        log.debug("join() {} + {} = {}", t1, t2, result);
        return result;
    }
}

结果:
16:44:26.541 [ForkJoinPool-1-worker-0] DEBUG c.MyTask - join() 1
16:44:26.541 [ForkJoinPool-1-worker-3] DEBUG c.MyTask - fork() 2 + {1}
16:44:26.541 [ForkJoinPool-1-worker-1] DEBUG c.MyTask - fork() 4 + {3}
16:44:26.541 [ForkJoinPool-1-worker-2] DEBUG c.MyTask - fork() 3 + {2}
16:44:26.544 [ForkJoinPool-1-worker-3] DEBUG c.MyTask - join() 2 + {1} = 3
16:44:26.544 [ForkJoinPool-1-worker-2] DEBUG c.MyTask - join() 3 + {2} = 6
16:44:26.544 [ForkJoinPool-1-worker-1] DEBUG c.MyTask - join() 4 + {3} = 10
10

16:44:26.544 [ForkJoinPool-1-worker-1] DEBUG c.MyTaskPlus - fork() {1,2} + {3,4} = ?
16:44:26.544 [ForkJoinPool-1-worker-3] DEBUG c.MyTaskPlus - join() 3 + 4 = 7
16:44:26.544 [ForkJoinPool-1-worker-2] DEBUG c.MyTaskPlus - join() 1 + 2 = 3
16:44:26.544 [ForkJoinPool-1-worker-1] DEBUG c.MyTaskPlus - join() {1,2} + {3,4} = 10
10

1.4、异步模式之工作线程

1.4.1、定义

让有限的工作线程(Worker Thread)来轮流异步处理无限多的任务,也可以将其归类为分工模式,它的典型实现就是线程池,也体现了经典设计模式中的享元模式。

例如,海底捞的服务员(线程),轮流处理每位客人的点餐任务,如果为每位客人都配一名专属的服务员,那么成本就太高了(对比另一种多线程设计模式:Thread-Per-Message)。

注意,不同任务类型应该使用不同的线程池,这样能够避免饥饿,并能提升效率。

例如,如果一个餐馆的工人既要招呼客人(任务类型A),又要到后厨做菜(任务类型B),显然效率不咋地,分成服务员(线程池A)与厨师(线程池B)更为合理,当然你能想到更细致的分工。

1.4.2、饥饿

固定大小的线程池会有饥饿现象:

  • 两个工人是同一个线程池中的两个线程;
  • 他们要做的事情:为客人点餐和到后厨做菜,这是两个阶段的工作。① 客人点餐:必须先点完餐,等菜做好,上菜,在此期间处理点餐的工人必须等待;② 后厨做菜:没啥说的,做就是了。
  • 比如工人A处理了点餐任务,接下来它要等着工人B把菜做好,然后上菜,他俩配合的蛮好;
  • 但是现在同时来了两个客人,这个时候工人A和工人B都去处理点餐了,这是没人做饭了,造成线程饥饿。
@Slf4j
public class Test1 {
    static final List<String> MENU = Arrays.asList("地三鲜", "辣子鸡丁", "烤鸡翅");
    static Random RANDOM = new Random();
    static String cooking() {
        return MENU.get(RANDOM.nextInt(MENU.size()));
    }
    public static void main(String[] args) {
        ExecutorService waiterPool = Executors.newFixedThreadPool(1);
        ExecutorService cookPool = Executors.newFixedThreadPool(1);

        waiterPool.execute(()->{
            log.debug("处理点餐...");
            Future<String> f = cookPool.submit(()->{
                log.debug("做菜");
                return cooking();
            });
            try {
                log.debug("上菜:{}", f.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });

        waiterPool.execute(()->{
            log.debug("处理点餐...");
            Future<String> f = cookPool.submit(() -> {
                log.debug("做菜");
                return cooking();
            });
            try {
                log.debug("上菜:{}", f.get());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        });
    }
}

结果:
14:19:10.486 [pool-1-thread-1] DEBUG demo.Test1 - 处理点餐...
14:19:10.489 [pool-2-thread-1] DEBUG demo.Test1 - 做菜
14:19:10.489 [pool-1-thread-1] DEBUG demo.Test1 - 上菜:地三鲜
14:19:10.490 [pool-1-thread-1] DEBUG demo.Test1 - 处理点餐...
14:19:10.490 [pool-2-thread-1] DEBUG demo.Test1 - 做菜
14:19:10.490 [pool-1-thread-1] DEBUG demo.Test1 - 上菜:辣子鸡丁

1.4.3、创建多少线程池合适

  • 过小会导致程序不能充分利用系统资源,容易导致饥饿;
  • 过大会导致更多的线程上下文切换,占用更多内存。

1、CPU密集型运算

通常采用CPU核数+1能够实现最优的CPU利用率,+1是保证当线程由于页缺失故障(操作系统)或其它原因导致暂停时,额外的这个线程就能顶上去,保证CPU时钟周期不被浪费。

2、I/O密集型运算

CPU不总是处理繁忙状态,例如,当你执行业务计算时,这时候会使用CPU资源,但当你执行I/O操作时,远程RPC调用时,包括进行数据库操作时,这时候CPU就闲下来了,你可以利用多线程提高它的效率。

经验公式如下:线程数 = 核数 * 期望CPU利用率 * 总时间(CPU计算时间 + 等待时间)/ CPU计算时间

例如4核CPU计算时间是10%,其他等待时间是90%,期望CPU被100%利用,套用公式 4 * 100% * 100% / 10% = 40 。

2、J.U.C

2.1、AQS原理

2.1.1、概述

AQS(Abstract Queued Synchronizer),是阻塞式锁和相关的同步器工具的框架。

特点:

  • 用state属性来表示资源的状态(分独占模式和共享模式),子类需要定义如何维护这个状态,控制如何获取锁和释放锁:① getState():获取state状态;② setState():设置state状态;③ compareAndSetState():乐观锁机制设置state状态;④ 独占模式是只有一个线程能够访问资源,而共享模式可以允许多个线程访问资源。
  • 提供了基于FIFO的等待队列,类似于Monitor的EntryList。
  • 条件变量来实现等待、唤醒机制,支持多个条件变量,类似于Monitor的WaitSet。

子类主要实现这样一些方法(默认抛出UnsupportedOperationException):

  • tryAcquire;
  • tryRelease;
  • tryAcquireShared。

获取锁 / 释放锁:

// 如果获取锁失败
        if (!tryAcquire(arg)) {
            // 入队,可以选择阻塞当前线程 park unpark
        }
        
        // 如果释放锁成功
        if (tryRelease(arg)) {
            // 让阻塞线程恢复运行
        }

自定义不可重入锁 代码演示:

@Slf4j
public class TestAqs {

    public static void main(String[] args) {
        MyLock lock = new MyLock();

        new Thread(() -> {
            lock.lock();
            log.debug("locking...");
            lock.lock();
            try {
                log.debug("locking...");
                sleep(1);
            } finally {
                log.debug("unlocking...");
                lock.unlock();
            }
        }, "t1").start();

        new Thread(() -> {
            lock.lock();
            try {
                log.debug("locking...");
            } finally {
                log.debug("unlocking...");
                lock.unlock();
            }
        }, "t2").start();
    }

    private static void sleep(int timeSeconds) {
        try {
            Thread.sleep(timeSeconds * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

// 自定义锁 - 不可重入锁
class MyLock implements Lock {
    // 独占锁 同步器
    class MySync extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            if (compareAndSetState(0, 1)) { // 保证状态修改的原子性
                // 加上了锁,并设置 owner 为当前线程
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            setExclusiveOwnerThread(null);
            setState(0); // 放在后面执行,因为 state 是 volatile 修饰的,有写屏障
            return true;
        }

        @Override // 是否持有独占锁
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        public Condition newCondition() {
            return new ConditionObject();
        }
    }

    private MySync sync = new MySync();

    @Override // 加锁(不成功进入等待队列)
    public void lock() {
        sync.acquire(1);
    }

    @Override // 加锁,可打断
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    @Override // 尝试加锁(尝试1次)
    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    @Override // 尝试加锁,带超时时间
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override // 解锁
    public void unlock() {
        sync.release(1);
    }

    @Override // 创建条件变量
    public Condition newCondition() {
        return sync.newCondition();
    }
}

结果:
16:10:38.852 [t1] DEBUG com.clp.multiThreads.test19.TestAqs - locking...

2.2、ReentrantLock 原理

2.3、ReentrantWriteLock 读写锁

当读操作远远高于写操作时,这时候使用读写锁让读-读可以并发,提高性能。类似于数据库中的select ... from ... lock in share mode 。

提供一个数据容器类内部分别使用读锁保护的read()方法,写锁保护数据的write()方法。

代码演示:

/**
 * 读写锁
 *      1、读 - 读 :并发
 *      2、读 - 写 :互斥
 *      3、写 - 写 :互斥
 */
@Slf4j
public class Test20 {
    public static void main(String[] args) throws InterruptedException {
        DataContainer dataContainer = new DataContainer();

        new Thread(() -> {
            dataContainer.write();
        }, "t1").start();

        Thread.sleep(100);
        new Thread(() -> {
            dataContainer.write();
        }, "t1").start();

    }
}

@Slf4j
class DataContainer {
    private Object data;
    private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    private ReentrantReadWriteLock.ReadLock rLock = rwLock.readLock();
    private ReentrantReadWriteLock.WriteLock wLock = rwLock.writeLock();

    public Object read() {
        log.debug("获取读锁...");
        rLock.lock();
        try {
            log.debug("读取");
            sleep(1);
            return data;
        } finally {
            log.debug("释放读锁...");
            rLock.unlock();
        }
    }

    public void write() {
        log.debug("获取写锁...");
        wLock.lock();
        try {
            log.debug("写入");
            sleep(1);
        } finally {
            log.debug("释放写锁...");
            wLock.unlock();
        }
    }

    private static void sleep(int timeSeconds) {
        try {
            Thread.sleep(timeSeconds * 1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果:
16:59:12.980 [t1] DEBUG com.clp.multiThreads.test20.DataContainer - 获取写锁...
16:59:12.982 [t1] DEBUG com.clp.multiThreads.test20.DataContainer - 写入
16:59:13.079 [t1] DEBUG com.clp.multiThreads.test20.DataContainer - 获取写锁...
16:59:13.983 [t1] DEBUG com.clp.multiThreads.test20.DataContainer - 释放写锁...
16:59:13.983 [t1] DEBUG com.clp.multiThreads.test20.DataContainer - 写入
16:59:14.983 [t1] DEBUG com.clp.multiThreads.test20.DataContainer - 释放写锁...

注意事项:

  • 读锁不支持条件变量;
  • 重入时升级不支持:即持有读锁的情况下去获取写锁,会导致获取写锁永久等待;
  • 重入时降级支持:即持有写锁的情况下去获取读锁。

 

2.4、Semaphore

2.5、CountdownLatch

2.6、CyclicBarrier

2.7、ConcurrentHashMap

2.8、ConcurrentLinkedQueue

2.9、BlockingQueue

 

posted on   啊噢1231  阅读(42)  评论(0编辑  收藏  举报

导航

统计信息

回到顶部
点击右上角即可分享
微信分享提示

1、线程池
1.1、自定义线程池
1.2、ThreadPoolExecutor
1.2.1、线程池状态
1.2.2、构造方法
1.2.3、newFixedThreadPool
1.2.4、newCachedThreadPool
1.2.5、newSingleThreadExecutor
1.2.6、提交任务
1.2.7、关闭线程池
1.2.8、任务调度线程池
1.2.9、Tomcat线程池
1.3、Fork / Join
1.3.1、概念
1.3.2、使用
1.4、异步模式之工作线程
1.4.1、定义
1.4.2、饥饿
1.4.3、创建多少线程池合适
2、J.U.C
2.1、AQS原理
2.1.1、概述
2.2、ReentrantLock 原理
2.3、ReentrantWriteLock 读写锁
2.4、Semaphore
2.5、CountdownLatch
2.6、CyclicBarrier
2.7、ConcurrentHashMap
2.8、ConcurrentLinkedQueue
2.9、BlockingQueue