java并发编程学习七——线程池

一、连接池

模拟实现一个数据库连接池

public class ConnPool {

    public static void main(String[] args) {
        ConnPool connPool =new ConnPool(2);
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                Connection connection = connPool.get();
                try {
                	//随机睡眠
                    Thread.sleep(new Random().nextInt(1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    connPool.free(connection);
                }

            },"线程"+i).start();
        }
    }
    //连接池大小
    private final int poolSize;
    //连接
    private MyConnection[] connections;
    //每个连接的状态,使用原子数组加cas确保线程安全
    private AtomicIntegerArray status;

    public ConnPool(int poolSize) {
        this.poolSize = poolSize;
        this.connections = new MyConnection[poolSize];
        this.status = new AtomicIntegerArray(poolSize);
        for (int i = 0; i < poolSize; i++) {
            connections[i] = new MyConnection("连接"+i);
            status.set(i, 0);
        }
    }

    public Connection get() {
        while (true) {
            for (int i = 0; i < poolSize; i++) {
                if (status.get(i) == 0) {
                    //status.set(i,1); set存在线程安全问题,改用cas
                    if (status.compareAndSet(i, 0, 1)) {
                        System.out.println(Thread.currentThread().getName()+"获得"+connections[i].getName());
                        return connections[i];
                    }
                }
            }
            //没有空闲线程
            synchronized (this) {
                try {
                    System.out.println(Thread.currentThread().getName()+"等待...");
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void free(Connection connection) {
        for (int i = 0; i < poolSize; i++) {
            if (connections[i] == connection) {
                //只有获取了连接的线程才能释放,不会发生竞争,因此没有线程安全问题
                status.set(i, 0);
                System.out.println(Thread.currentThread().getName()+"释放"+connections[i].getName());
                break;
            }
        }
        synchronized (this) {
            this.notifyAll();
        }
    }
}

class MyConnection implements Connection {
    private String name;

    public MyConnection(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    //实现方法省略
}

二、线程池

模拟实现一个线程池

public class TestThreadPool {
    public static void main(String[] args) {
        ThreadPool threadPool = new ThreadPool(2, 3, TimeUnit.SECONDS, 2);
        for (int i = 0; i < 6; i++) {
            int j = i;
            threadPool.execute(() -> {
                System.out.println("任务" + j);
                try {
                    Thread.sleep(1000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            });
        }
    }
}

class ThreadPool {
    //核心线程数
//    private final int coreSize;
    //最大线程数
    private final int maxSize;
    //线程存活时间
    private final long timeout;
    //时间单位
    private final TimeUnit unit;
    //工作线程集合
    private final Set<Worker> workers = new HashSet<>();
    //任务队列
    private final BlockingQueue<Runnable> blockingQueue;
    //拒绝策略
    private final RejectedHandler rejectedHandler;

    public ThreadPool(int maxSize, long timeout, TimeUnit unit, int queueSize) {
        this.maxSize = maxSize;
        this.timeout = timeout;
        this.unit = unit;
        this.blockingQueue = new BlockingQueue<>(queueSize);
        rejectedHandler = (Runnable task) -> {
//            throw new RuntimeException();
//            task.run();

        };
    }

    public void execute(Runnable task) {
        //有剩余线程,直接执行任务
        synchronized (workers) {
            if (workers.size() < maxSize) {
                Worker worker = new Worker(task);
                worker.start();
                workers.add(worker);
            } else {
                //没有剩余线程加入任务队列
//            blockingQueue.put(task);
                if (!blockingQueue.offer(task, timeout, unit)) {
                    rejectedHandler.rejeced(task);
                }
            }
        }
    }

    //工作线程
    class Worker extends Thread {
        private Runnable task;

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

        @Override
        public void run() {

            //任务完成之后取队列任务继续执行
//        while (task != null || (task = blockingQueue.take()) != null) {
            while (task != null || (task = blockingQueue.poll(timeout, unit)) != null) {
                System.out.println(this.getName());
                task.run();
                task = null;
            }
            //线程结束之后移除,否则将导致无法创建新的线程执行任务
            synchronized (workers) {
                workers.remove(this);
                System.out.println(this+"被移除");
            }
        }

    }

}


class BlockingQueue<T> {
    //任务队列
    private final Deque<T> deque = new ArrayDeque<>();
    //队列最大数量
    private final int dequeSize;
    //锁
    private final ReentrantLock lock = new ReentrantLock();
    //队列已空
    private final Condition emptyList = lock.newCondition();
    //队列已满
    private final Condition fullList = lock.newCondition();

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

    //阻塞获取一个任务
    T take() {
        lock.lock();
        try {
            while (deque.isEmpty()) {
                //进入队列已空的条件等待
                try {
                    emptyList.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            T t = deque.removeFirst();
            //唤醒队列已满的等待线程
            fullList.signalAll();
            return t;
        } finally {
            lock.unlock();
        }


    }

    //获取一个任务带超时
    T poll(long timeout, TimeUnit unit) {
        long nanos = unit.toNanos(timeout);
        lock.lock();
        try {
            while (deque.isEmpty()) {
                //进入队列已空的条件等待
                try {
                    //进入队列已空的条件等待,无剩余等待时间就返回
                    if (nanos <= 0) {
                        System.out.println(Thread.currentThread().getName() + "结束获取任务等待");
                        return null;
                    }
                    //返回剩余等待时间
                    nanos = emptyList.awaitNanos(nanos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            T t = deque.removeFirst();
            //唤醒队列已满的等待线程
            fullList.signalAll();
            return t;
        } finally {
            lock.unlock();
        }

    }

    //阻塞添加一个任务
    void put(T t) {

        lock.lock();
        try {
            while (deque.size() == dequeSize) {
                try {
                    fullList.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            deque.addLast(t);
            emptyList.signalAll();

        } finally {
            lock.unlock();
        }
    }


    //加入一个任务带超时
    boolean offer(T t, long timeout, TimeUnit unit) {
        long nanos = unit.toNanos(timeout);

        lock.lock();
        try {
            while (deque.size() == dequeSize) {
                try {
                    //进入队列已满的条件等待,无剩余等待时间就返回
                    if (nanos <= 0) {
                        System.out.println("结束添加任务等待" + t);
                        return false;
                    }
                    nanos = fullList.awaitNanos(nanos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            deque.addLast(t);
            System.out.println("添加任务" + t);
            emptyList.signalAll();
            return true;
        } finally {
            lock.unlock();
        }
    }

    int size() {
        lock.lock();
        try {
            return deque.size();
        } finally {
            lock.unlock();
        }

    }
}

interface RejectedHandler {
    void rejeced(Runnable task);
}

三、ThreadPoolExecutor

ThreadPoolExecutor使用一个int类型变量的高3位表示线程状态,后29位表示线程数量。
在这里插入图片描述
注意这里的高3位是补码,111表示的是-1。状态和数量在修改的时候需要使用cas操作保证原子性,合为一个数字之后如果同时更改,可以减少一次cas。而分别修改高低位,可以使用|运算将两个数字合并之后再用cas修改变量。
构造方法:

	/**
     * @param corePoolSize 核心线程数(最多保留的线程数)
     * @param maximumPoolSize 最大线程数
     * @param keepAliveTime 线程空闲时间-针对救急线程
     * @param unit 时间单位-针对救急线程
     * @param workQueue 工作阻塞队列
     * @param threadFactory 线程工厂
     * @param handler 拒绝策略
     */
   public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
                              }

工作方式:

  • 线程池中刚开始没有线程,当有任务提交时才创建线程,此时创建的是核心线程
  • 任务太多,核心线程数目达到上限corePoolSize之后,任务会被放入阻塞队列workQueue,等待线程执行
  • 如果workQueue是有界队列,随着任务继续增加,阻塞队列满了之后,就会创建救急线程。救急线程最大数目=maximumPoolSize-corePoolSize
  • 如果线程数达到maximumPoolSize,任然有新的任务提交,就会执行拒绝策略
  • 当任务高峰过去之后,超过corePoolSize数目的救急线程会结束,从而节省资源。救急线程的存活时间由keepAliveTime和unit控制

根据这个构造方法,JDK在Executors类中提供了几个静态工厂方法,来创建不同用途的线程池,不用我们指定线程池的参数

3.1 newFixedThreadPool

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

newFixedThreadPool是固定大小的线程池。这个线程池的核心线程数和最大线程数相同,即所有线程都是核心线程数,创建之后不会结束。LinkedBlockingQueue容量默认int最大值,新的任务会不断往里面添加。适应于任务量已知,任务时间较长的情况

public class newFixedThreadPool {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(2, r -> {
            AtomicInteger i = new AtomicInteger(1);
            return new Thread(r, "mypool_t" + i.getAndIncrement());
        });

        executorService.submit(() -> {
            System.out.println(Thread.currentThread().getName() + "1");
        });
        executorService.submit(() -> {
            System.out.println(Thread.currentThread().getName() + "2");
        });
        executorService.submit(() -> {
            System.out.println(Thread.currentThread().getName() + "3");
        });
    }
}

在这里插入图片描述

任务执行完之后,程序仍然在运行,说明核心线程数在空转,线程池没有结束。

3.2 newCachedThreadPool

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

newCachedThreadPool没有核心线程,与newFixedThreadPool相反,所有线程都是救急线程,最大数量不超过int最大值。意味着新任务提交时只要没有空闲线程就创建新线程。线程空闲存活时间为60s,增加了可复用性。SynchronousQueue是一个特殊队列,其本身是没有容量大小,放一个数据到队列中不能立刻返回的,必须等待放进去的数据被消费掉了,才能够返回。适用于任务密集,每个任务执行时间较短的情况

3.3 newSingleThreadExecutor

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

newSingleThreadExecutor是固定大小为1的线程池,它与newFixedThreadPool相似,只是数量固定是1不能修改,而newFixedThreadPool(1)可以修改大小。与自定义一个线程串行执行任务的区别是,当任务执行失败线程结束时,线程池会重新创建一个线程来执行后面的任务。适应于多个任务排队执行的情况

3.4 newScheduledThreadPool

	public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
        return new ScheduledThreadPoolExecutor(corePoolSize);
    }
    public ScheduledThreadPoolExecutor(int corePoolSize) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue());
    }

newScheduledThreadPool是延时线程池,需要提供一个核心线程数。最大线程数为int最大值,也有线程过多的风险。DelayedWorkQueue的默认初始值为16,会自动扩容,最大值也是int最大值。适用于任务量已知且需要延迟执行的情况

public class newScheduledThreadPool {
    public static void main(String[] args) {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(2);
        System.out.println("begin");
        pool.schedule(() -> System.out.println(Thread.currentThread().getName() + "1秒后打印"), 1, TimeUnit.SECONDS);

        pool.schedule(() -> {
            System.out.println(Thread.currentThread().getName() + "1秒后打印");
            return "end";
        }, 1, TimeUnit.SECONDS);

        pool.execute(() -> System.out.println(Thread.currentThread().getName() + "execute"));

    }
}

3.5 newWorkStealingPool

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

newWorkStealingPool使用的是ForkJoinPool任务拆分合并线程池,线程数量固定为CPU核数,最大限度利用CPU进行密集运算。每个线程都有一个任务队列存放任务,当自己队列的任务执行完会窃取其他线程的队列任务,因此又叫抢占式线程池。适用于任务密集情况,每个任务执行时间长短均可

public class newWorkStealingPool {
    public static void main(String[] args) {
        //创建一个具有抢占式操作的线程池 1.8 之后新增 每个线程都有一个任务队列存放任务
        ExecutorService pool = Executors.newWorkStealingPool(Runtime.getRuntime().availableProcessors());
        LinkedBlockingDeque<Future<String>> strings = new LinkedBlockingDeque<>();
        // CPU 核数
        System.out.println(Runtime.getRuntime().availableProcessors());

        for (int i = 0; i < Runtime.getRuntime().availableProcessors() + 1; i++) {
            Future<String> submit = pool.submit(() -> {
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return "展示线程:" + Thread.currentThread().getName();
            });
            strings.offer(submit);
        }
        pool.shutdown();

        strings.forEach(f -> {
            try {
                System.out.println(f.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });
    }
}

四、线程池应用

4.1 多个线程池分工

public class DivisionThreadPool {
    static List<String> list = Arrays.asList("辣子鸡", "水煮鱼", "粉蒸肉");

    public static void main(String[] args) {

//        ExecutorService pool = Executors.newFixedThreadPool(2);
        //使用两个线程池分工
        ExecutorService waiterPool = Executors.newFixedThreadPool(1);
        ExecutorService cookPool = Executors.newFixedThreadPool(1);

        waiterPool.execute(() -> {
            System.out.println("点菜");
            Future<String> submit = cookPool.submit(() -> cooking());
            try {
                System.out.println("上菜" + submit.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });

        waiterPool.execute(() -> {
            System.out.println("点菜");
            Future<String> submit = cookPool.submit(() -> cooking());
            try {
                System.out.println("上菜" + submit.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        });

    }

    private static String cooking() {
        return list.get(new Random().nextInt(3));
    }
}

4.2 正确处理线程池异常

执行任务时发生异常的处理

public class ThreadPoolException {
    public static void main(String[] args){
        ExecutorService pool = Executors.newCachedThreadPool();
        pool.execute(() -> {
            System.out.println("test");
            //任务自己处理异常
            try {
                int i = 1 / 0;
                System.out.println(i);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        Future<Boolean> future = pool.submit(() -> {
            System.out.println("test");
            int i = 1 / 0;
            System.out.println(i);
            return true;
        });
        //使用future获取异常
        try {
            future.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }


    }
}

4.3 定时任务

使用newScheduledThreadPool实现每周三20点执行任务

public class ScheduledThreadPoolTimer {
    public static void main(String[] args) {
        ScheduledExecutorService pool = Executors.newScheduledThreadPool(1);

        long period = 24 * 60 * 60 * 1000 * 7;
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime nextTime = now.withHour(20).withMinute(0).withSecond(0).withNano(0).with(DayOfWeek.THURSDAY);
        System.out.println(nextTime);
        long initialDelay = Duration.between(now, nextTime).toMillis();
        if (now.isAfter(nextTime)) {
            initialDelay = period - initialDelay;
        }
 		 /**
         *  第一次等待initialDelay后开始执行,第二次在initialDelay+period开始执行
         *  如果第一次>period,那么第二次开始时间为initialDelay+exetime
         *  意思是后一次执行必须等待前一次执行完
         *  类似方法scheduleWithFixedDelay需再上一次任务执行完之后等待period
         *  第一次开始时间为initialDelay,执行时间为exetime,第二次开始时间为initialDelay+exetime+period
         */
        pool.scheduleAtFixedRate(() -> {
            System.out.println("每周三20点执行任务");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, initialDelay, period, TimeUnit.MICROSECONDS);
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值