Java多线程并发编程-线程池

Java多线程并发编程-线程池

  • 线程池
    • 问题思考
    • 线程池原理
      • 任务用什么表示
      • 仓库用什么:BlockingQueue
    • 自己实现一个线程池
    • JDK线程池API
      • Executor
      • ExecutorService
      • ScheduledExecutorService
      • Callable
      • Future
      • ThreadPoolExecutor
      • Executors

线程池

问题思考

  • 问题1、用多线程的目的是什么?

充分利用 CPU 资源,并发做多件事。

  • 问题2、单核 CPU 机器上适不适合用多线程?

适合,如果是单线程,线程中需要等待 IO 时,此时 CPU 就空闲出来了。

  • 问题3、线程什么时候会让出 CPU?

阻塞时,wait,await,等待IO,sleep,yield,执行结束了。。

  • 问题4、线程是什么?

执行任务的基本单位。一条代码执行流,完成一组代码的执行。这一组代码,我们往往称为一个任务。

  • 问题5、CPU 做的是什么工作?

执行代码
在这里插入图片描述

  • 问题6、线程是不是越多越好?

造卡车(线程)要不要时间?一次使用,用完了得销毁,销毁要不要耗时间?
1、线程在java中是一个对象,每一个java线程都需要有一个操作系统线程支持。线程创建、销毁需要时间。如果 创建时间+销毁时间 > 执行任务时间 就很不合算。

造很多的卡车,得需要空间来放它们,会不会造成内存紧张?
2、java对象占用堆内存,操作系统线程占用系统内存,根据 jvm 规范,一个线程默认最大栈大小 1M,这个栈空间是需要从系统内存中分配的。
线程过多,会消耗很多内存
3、操作系统需要频繁切换线程上下文(大家都想被运行),影响性能。

  • 问题7、该如何正确使用多线程

多线程目的:充分利用 CPU 并发做事
线程的本质:将代码送给 CPU 执行
用合适数量的卡车不断运送代码即可,这个合适数量的线程就构成了一个池。
有任务要执行,就放入池中,池中的一个线程将把任务运送到 CPU 执行。

线程池原理

Java多线程并发编程-线程池_第1张图片

  • 接收任务,放入仓库
  • 工作线程从仓库取任务,执行
  • 当没有任务时,线程阻塞,当有任务时唤醒线程执行

任务用什么表示

  • Runnable
  • Callable

仓库用什么:BlockingQueue

  • 阻塞队列,线程安全的:在队列为空时获取阻塞,在队列满时放入阻塞。
  • BlockingQueue的方法以四种形式出现,对于不能立即满足但可能在将来某一时刻可以满足的操作,这四种形式的处理方式不同:第一种是抛出异常,第二种是返回一个特殊值(null或false,具体取决于操作),第三种是在操作可以成功前,无限期地阻塞当前线程,第四种是在放弃前只在给定的最大时间限制内阻塞。总结如下表:
方法/处理方法 抛出异常 返回特殊值 一直阻塞 超时退出
插入方法 add(e) offer(e) put(e) offer(e, time, unit)
移出方法 remove() poll() take() poll(time, unit)
检查方法 element() peek() 不可用 不可用
  • 问题8、如何确定合适数量的线程

如果是计算型任务?
CPU数量的1-2倍

如果是IO型任务?
需啊哟多一些线程,要根据具体IO阻塞时长进行考量决定。如 tomcat 中默认的最大线程数为200。也可以考虑根据需要在一个最小数量和最大数量间自动增减线程数。

自己实现一个线程池

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import static java.lang.System.out;

/**
 * @description: 自己实现一个线程池
 * @author: 10217629
 * @date: 2019/4/12
 */
public class FixedSizeThreadPool {
    // 思考:如果我们需要手写一个线程池,需要什么东西?

    // 1. 需要一个仓库
    private BlockingQueue<Runnable> blockingQueue;

    // 2. 需要一个线程集合
    private List<Thread> workers;

    // 3. 需要一个干活的线程
    public static class Worker extends Thread {
        private FixedSizeThreadPool pool;

        public Worker(FixedSizeThreadPool pool) {
            this.pool = pool;
        }

        @Override
        public void run() {
            // 去仓库拿东西
            while (this.pool.isWorking || this.pool.blockingQueue.size() > 0) {
                Runnable task = null;
                try {
                    if (this.pool.isWorking) {
                        // 阻塞方式
                        task = this.pool.blockingQueue.take();
                    } else {
                        // 非阻塞方式
                        task = this.pool.blockingQueue.poll();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
                if (task != null) {
                    task.run();
                    out.println("线程:" + Thread.currentThread().getName() + "执行完毕");
                }
            }
        }
    }

    // 4. 需要初始化规定好仓库的大小以及集合,同时把线程准备就绪
    public FixedSizeThreadPool(int poolSize, int taskSize) {
        if (poolSize <= 0 || taskSize <= 0) {
            throw new IllegalArgumentException("非法参数");
        }
        this.blockingQueue = new LinkedBlockingQueue<Runnable>(taskSize);
        // Collections.synchronizedList:处理线程安全
        this.workers = Collections.synchronizedList(new ArrayList<Thread>());
        for (int i = 0; i < poolSize; i++) {
            Worker worker = new Worker(this);
            worker.start();
            workers.add(worker);
        }
    }

    // 5. 需要向仓库放的代码(阻塞)
    public void excute(Runnable task) {
        try {
            this.blockingQueue.put(task);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    // 6. 需要向仓库放的代码(非阻塞)
    public boolean submit(Runnable task) {
        if (this.isWorking) {
            return this.blockingQueue.offer(task);
        } else {
            return false;
        }
    }

    // 7. 需要一个关闭方法
    // a. 关闭的时候,仓库要停止新的线程进来
    // b. 关闭的时候,如果仓库没有东西,我们要执行完
    // c. 关闭的时候,如果去仓库拿东西,我们就不能阻塞了
    // d. 关闭的时候,把阻塞的线程全部中断
    private volatile boolean isWorking = true;
    public void shutdown() {
        this.isWorking = false;
        for (Thread thread : workers) {
            if (thread.getState().equals(Thread.State.WAITING) || thread.getState().equals(Thread.State.BLOCKED)) {
                thread.interrupt();
            }
        }
    }

    // 测试
    public static void main(String[] args) {
        FixedSizeThreadPool pool = new FixedSizeThreadPool(3, 6);
        for (int i = 0; i < 6; i++) {
            pool.submit(new Runnable() {
                @Override
                public void run() {
                    out.println("一个线程被放入到我们的仓库中...");
                    try {
                        Thread.sleep(2000L);
                    } catch (InterruptedException e) {
                        out.println("有线程被中断");
                    }
                }
            });
        }
        pool.shutdown();
    }
}

JDK线程池API

  • Java并发包提供了丰富的线程池实现

Executor

  • Executor 接口
    Java多线程并发编程-线程池_第2张图片

ExecutorService

  • ExecutorService 加入了关闭方法和对Callable、Future的支持
    Java多线程并发编程-线程池_第3张图片

ScheduledExecutorService

  • ScheduledExecutorService 加入对定时任务的支持
    Java多线程并发编程-线程池_第4张图片
    Java多线程并发编程-线程池_第5张图片

Callable

  • Callable 对 Runnable 的改进:可以返回值,可以抛出异常
    Java多线程并发编程-线程池_第6张图片

Future

  • Future 异步任务监视器,让提交者可以监控任务的执行
    Java多线程并发编程-线程池_第7张图片

ThreadPoolExecutor

  • ThreadPoolExecutor 线程池标准实现
    Java多线程并发编程-线程池_第8张图片
    Java多线程并发编程-线程池_第9张图片

Executors

  • Executors 创建线程池的工厂类,快速得到线程池的工具类,减轻我们的任务,它的工厂方法
    Java多线程并发编程-线程池_第10张图片
  • newFixedThreadPool(int nThreads)创建一个固定大小、任务队列容量无界的线程池。池的核心线程数 = 最大线程数 = nThreads。
  • newCachedThreadPool()创建的是一个大小无界的缓冲线程池。它的任务队列是一个同步队列。任务加入到池中,如果池中有空闲线程,则用空闲线程执行,如无则创建新线程执行。池中的线程空闲超过60秒,池中的核心线程数 = 0 最大线程数 = Integer.MAX_VALUE。
  • newSingleThreadExecutor()只有一个任务队列的单一线程池。该线程池确保任务按加入的顺序一个一个依次执行,任何时刻只有一个线程来执行无界队列的任务。当唯一的线程因任务异常终止时,将创建一个新的线程类继续执行后续的任务。单一线程与 newFixedThreadPool的区别在于:单一线程池的大小是不能再改变的。
  • newScheduledThreadPool(int corePoolSize):能定时执行任务的线程池。该池的核心线程数由参数指定,最大线程数 = Integer.MAX_VALUE。
  • newWorkStealingPool():以当前系统可用处理器数作为并行级别创建的 work-stealing thread pool。
  • newWorkStealingPool(int parallelism):以 parallelism 指定的并行级别创建的 work-stealing thread pool(ForkJoinPool)。

你可能感兴趣的:(Java多线程并发编程)