JUC-02 (线程池)

目录

阻塞队列

接口架构图

API 的使用

尽量按组匹配使用

解释:

代码测试

一直阻塞

超时退出

SynchronousQueue 同步队列

线程池的三大方法

三大方法说明

Executors.newFixedThreadPool(int)

Executors.newSingleThreadExecutor()

Executors.newCachedThreadPool();

但这三个创建线程池的方法都不用:

ThreadPoolExecutor 七大参数(使用)

参数理解:

ThreadPoolExecutor 底层工作原理即实现

线程是否越多越好?

常用辅助类:

CountDownLatch  计数减法

CyclicBarrier  计数加法

Semaphore 信号量,信号灯,信号; 作用:抢车位

利用CountDownLatch和线程池组合案列

资源类

调用类


阻塞队列

阻塞队列是一个队列,在数据结构中起的作用如下图:
当队列是空的,从队列中 获取 元素的操作将会被阻塞。
当队列是满的,从队列中 添加 元素的操作将会被阻塞。
试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素。
试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个元素或者完全清空,使队列变得空闲起来并后续新增。
阻塞队列的用处:
在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤起。
为什么需要 BlockingQueue
        好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这BlockingQueue 都给你一手包办了。
        在 concurrent 包发布以前,在多线程环境下,我们每个程序员都必须自己去控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。

接口架构图

ArrayBlockingQueue :由数组结构组成的有界阻塞队列。
LinkedBlockingQueue :由链表结构组成的有界(默认值为: integer.MAX_VALUE )阻塞队 列。
PriorityBlockingQueue :支持优先级排序的无界阻塞队列
DelayQueue :使用优先级队列实现的延迟无界阻塞队列。
SynchronousQueue :不存储元素的阻塞队列,也即单个元素的队列。
LinkedTransferQueue :由链表组成的无界阻塞队列。
LinkedBlockingDeque :由链表组成的双向阻塞队列。

API 的使用

尽量按组匹配使用

解释:

代码测试

一直阻塞
public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        // 队列大小
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
        // 一直阻塞
        blockingQueue.put("a");
        blockingQueue.put("b");
        blockingQueue.put("c");
        // blockingQueue.put("d");
        System.out.println(blockingQueue.take()); // a
        System.out.println(blockingQueue.take()); // b
        System.out.println(blockingQueue.take()); // c
        System.out.println(blockingQueue.take()); // 阻塞不停止等待
    }
}
超时退出
public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        // 队列大小
        ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue<>(3);
        // 一直阻塞
        blockingQueue.offer("a");
        blockingQueue.offer("b");
        blockingQueue.offer("c");
        blockingQueue.offer("d",3L,TimeUnit.SECONDS); // 等待3秒超时退出
        System.out.println(blockingQueue.poll()); // a
        System.out.println(blockingQueue.poll()); // b
        System.out.println(blockingQueue.poll()); // c
        System.out.println(blockingQueue.poll(3L,TimeUnit.SECONDS)); // 阻塞不停止等待
    }
}
SynchronousQueue 同步队列

SynchronousQueue 没有容量。

与其他的 BlockingQueue 不同, SynchronousQueue 是一个不存储元素的 BlockingQueue
每一个 put 操作必须要等待一个 take 操作,否则不能继续添加元素,反之亦然。
import jdk.nashorn.internal.ir.Block;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

public class SynchronousQueueDemo {
    public static void main(String[] args) {
        BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
        new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + " put 1");
                blockingQueue.put("1");
                System.out.println(Thread.currentThread().getName() + " put 2");
                blockingQueue.put("2");
                System.out.println(Thread.currentThread().getName() + " put 3");
                blockingQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "T1").start();
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + blockingQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + blockingQueue.take());
                TimeUnit.SECONDS.sleep(3);
                System.out.println(Thread.currentThread().getName() + blockingQueue.take());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "T2").start();
    }
}


线程池的三大方法

Java 中的线程池是通过 Executor 框架实现的,该框架中用到了 Executor Executors
ExecutorService ThreadPoolExecutor 这几个类。

三大方法说明

Executors.newFixedThreadPool(int)

执行长期任务性能好,创建一个线程池,一池有 N 个固定的线程,有固定线程数的线程。

public class MyThreadPoolDemo {
    public static void main(String[] args) {
        // 池子大小 5
        ExecutorService threadPool = Executors.newFixedThreadPool(5);
        try {
            // 模拟有10个顾客过来银行办理业务,池子中只有5个工作人员受理业务
            for (int i = 1; i <= 10; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " 办理业务");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown(); // 用完记得关闭
        }
    }
}

Executors.newSingleThreadExecutor()

只有一个线程
public class MyThreadPoolDemo {
    public static void main(String[] args) {
        // 有且只有一个固定的线程
        ExecutorService threadPool = Executors.newSingleThreadExecutor();
        try {
            // 模拟有10个顾客过来银行办理业务,池子中只有1个工作人员受理业务
            for (int i = 1; i <= 10; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " 办理业务");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown(); // 用完记得关闭
        }
    }
}

Executors.newCachedThreadPool();

执行很多短期异步任务,线程池根据需要创建新线程,但在先构建的线程可用时将重用他们。
可扩容,遇强则强
public class MyThreadPoolDemo {
    public static void main(String[] args) {
// 一池N线程,可扩容伸缩
        ExecutorService threadPool = Executors.newCachedThreadPool();
        try {
            // 模拟有10个顾客过来银行办理业务,池子中只有N个工作人员受理业务
            for (int i = 1; i <= 10; i++) {
            // 模拟延时看效果
            // try {
            // TimeUnit.SECONDS.sleep(1);
            // } catch (InterruptedException e) {
            // e.printStackTrace();
            // }
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " 办理业务");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown(); // 用完记得关闭
        }
    }
}

但这三个创建线程池的方法都不用:

只能使用自定义的
 

ThreadPoolExecutor 七大参数(使用)

操作:查看三大方法的底层源码,发现本质都是调用了 new ThreadPoolExecutor ( 7 大参数 )
// 源码
public ThreadPoolExecutor(int corePoolSize,
        int maximumPoolSize,
        long keepAliveTime,
        TimeUnit unit,
        BlockingQueue<Runnable> workQueue,
        ThreadFactory threadFactory,
        RejectedExecutionHandler handler){

        if(corePoolSize< 0||maximumPoolSize<=0||maximumPoolSize<corePoolSize ||keepAliveTime< 0)
        throw new IllegalArgumentException();

        if(workQueue==null||threadFactory==null||handler==null)
        throw new NullPointerException();

        this.acc=System.getSecurityManager()==null?
        null:
        AccessController.getContext();
        this.corePoolSize=corePoolSize;
        this.maximumPoolSize=maximumPoolSize;
        this.workQueue=workQueue;
        this.keepAliveTime=unit.toNanos(keepAliveTime);
        this.threadFactory=threadFactory;
        this.handler=handler;
        }

参数理解:

corePoolSize核心线程数。在创建了线程池后,线程中没有任何线程,等到有任务到来时才创建线程去执行任务。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize 后,就会把到达的任务放到缓存队列当中。
maximumPoolSize 最大线程数。表明线程中最多能够创建的线程数量,此值必须大于等于 1
keepAliveTime :空闲的线程保留的时间。
TimeUnit :空闲线程的保留时间单位。
        TimeUnit . DAYS ; //
        TimeUnit . HOURS ; // 小时
        TimeUnit . MINUTES ; // 分钟
        TimeUnit . SECONDS ; //
        TimeUnit . MILLISECONDS ; // 毫秒
        TimeUnit . MICROSECONDS ; // 微妙
        TimeUnit . NANOSECONDS ; // 纳秒
BlockingQueue< Runnable> 阻塞队列,存储等待执行的任务。参数有    ArrayBlockingQueue 、 LinkedBlockingQueue、 SynchronousQueue 可选。
ThreadFactory 线程工厂,用来创建线程,一般默认即可
RejectedExecutionHandler 队列已满,而且任务量大于最大线程的异常处理策略。有以下取值:
        ThreadPoolExecutor . AbortPolicy : 丢弃任务并抛出 RejectedExecutionException 异常。
        ThreadPoolExecutor . DiscardPolicy :也是丢弃任务,但是不抛出异常。
        ThreadPoolExecutor . DiscardOldestPolicy :丢弃队列最前面的任务,然后重新尝试执                                                                                行任务 (重复此过程)
        ThreadPoolExecutor . CallerRunsPolicy :由调用线程处理该任务

ThreadPoolExecutor 底层工作原理即实现

1. 在创建了线程池后,开始等待请求。
2. 当调用 execute() 方法添加一个请求任务时,线程池会做出如下判断:
        1. 如果正在运行的线程数量小于 corePoolSize ,那么马上创建线程运行这个任务:
        2. 如果正在运行的线程数量大于或等于 corePoolSize ,那么将这个任务放入队列:
        3. 如果这个时候队列满了且正在运行的线程数量还小于 maximumPoolSize ,那么还是                要创建非核心线程立刻运行这个任务;
        4. 如果队列满了且正在运行的线程数量大于或等于 1Size ,那么线程池会启动饱和拒绝              策略来执 行。
3. 当一个线程完成任务时,它会从队列中取下一个任务来执行。
4. 当一个线程无事可做超过一定的时间 (keepA1iveTime) 时,线程会判断:
        如果当前运行的线程数大于coreP 1Size ,那么这个线程就被停掉。
        所以线程池的所有任务完成后,它最终会收缩到 corePoolSize 的大小。
例如:
corePoolSize=2        核心线程数2
new LinkedBlockingDeque<>(3)    队列3
maximumPoolSize = 8        最大线程数
Executors.defaultThreadFactory()
new ThreadPoolExecutor.DiscardPolicy())
刚开始:
1~2 个任务立即被受理(核心大小 core
3~5个任务  2个任务立即执行 剩下的任务进入队列( Queue
6~8人任务 2个任务立即执行 3个任务进入队列 剩下的开启新线程进行执行(从第3个任务开始先进入的任务先进入队列)
9个任务以上 大于8的任务直接丢弃且不抛异常
代码:
public class MyThreadPoolDemo {
    public static void main(String[] args) {
        // 获得CPU的内核数
        System.out.println(Runtime.getRuntime().availableProcessors());
        // 自定义 ThreadPoolExecutor
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,//核心线程数
                5,//最大线程数
                2L,//空闲线程保留时间
                TimeUnit.SECONDS,//保留时间单位
                new LinkedBlockingDeque<>(3),//队列
                Executors.defaultThreadFactory(),//线程创建工厂
                new ThreadPoolExecutor.DiscardPolicy());//超过最大线程数拒绝策略
        try {
            // 模拟有6,7,8,9,10个顾客过来银行办理业务,观察结果情况
            // 最大容量为:maximumPoolSize + workQueue = 最大容量数
            for (int i = 1; i <= 19; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + " 办理业务");
                });
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown(); // 用完记得关闭
        }
    }
}

线程是否越多越好?

一个计算为主的程序(专业一点称为 CPU 密集型程序 )。多线程跑的时候,可以充分利用起所有的 cpu核心,比如说4 个核心的 cpu, 4 个线程的时候,可以同时跑 4 个线程的运算任务,此时是最大效率。但是如果线程远远超出cpu 核心数量 反而会使得任务效率下降,因为频繁的切换线程也是要消耗时间的。因此对于cpu 密集型的任务来说,线程数等于cpu数是最好的了。
如果是一个磁盘或网络为主的程序( IO 密集型 )。一个线程处在 IO 等待的时候,另一个线程还可以在CPU里面跑,有时候 CPU 闲着没事干,所有的线程都在等着 IO ,这时候他们就是同时的了,而单线程的话此时还是在一个一个等待的。我们都知道IO 的速度比起 CPU 来是慢到令人发指的。所以开多线程,比方说多线程网络传输,多线程往不同的目录写文件,等等。
此时线程数等于 IO 任务数是最佳的

常用辅助类

CountDownLatch  计数减法

CountDownLatch 主要有两个方法,当一个或多个线程调用 await 方法时,这些线程会阻塞
其他线程调用 CountDown 方法会将计数器减 1 (调用 CountDown 方法的线程不会阻塞)
当计数器变为 0 时, await 方法阻塞的线程会被唤醒,继续执行
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        // 计数器
        CountDownLatch countDownLatch = new CountDownLatch(6);
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "Start");
                        countDownLatch.countDown(); // 计数器-1
            }, String.valueOf(i)).start();
        }
        //阻塞等待计数器归零
        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + " End");
    }

    /**
     * 顺序不一定,结果诡异,达不到预期的最后End
     */
    public void test1() {
        for (int i = 1; i <= 6; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "Start");
            }, String.valueOf(i)).start();
        }
        System.out.println(Thread.currentThread().getName() + " End");
    }
}

CyclicBarrier  计数加法

作用:和上面的减法相反,这里是加法,好比集齐 7 个龙珠召唤神龙,或者人到齐了再开会!
public class CyclicBarrierDemo {
    public static void main(String[] args) {
// CyclicBarrier(int parties, Runnable barrierAction)
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("召唤神龙成功");
        });
        for (int i = 1; i <= 7; i++) {
            final int tempInt = i;
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + "收集了 第"+ tempInt +" 颗龙珠");
                try {
                    cyclicBarrier.await(); // 等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

Semaphore 信号量,信号灯,信号; 作用:抢车位

在信号量上我们定义两种操作:
        acquire(获取)
                当一个线程调用 acquire 操作时,他要么通过成功获取信号量(信号量 -1
                要么一直等下去,直到有线程释放信号量,或超时
        release (释放)
                实际上会将信号量的值 + 1,然后唤醒等待的线程。
        (release)
信号量主要用于两个目的:一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。
public class SemaphoreDemo {
    public static void main(String[] args) {
        // 模拟资源类,有3个空车位
        Semaphore semaphore = new Semaphore(3);
        for (int i = 1; i <= 6; i++) { // 模拟6个车
            new Thread(()->{
                try {
                    semaphore.acquire(); // acquire 得到
                    System.out.println(Thread.currentThread().getName()+" 抢到了车位");
                            TimeUnit.SECONDS.sleep(3); // 停3秒钟
                    System.out.println(Thread.currentThread().getName()+" 离开了车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    semaphore.release(); // 释放这个位置
                }
            },String.valueOf(i)).start();
        }
    }
}

利用CountDownLatch和线程池组合案列

资源类

package com.blockchain.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.ThreadPoolExecutor;

/**
 * @BelongsProject: chaincto_app_backend
 * @BelongsPackage: com.blockchain.config
 * @Author: KaySen
 * @CreateTime: 2022-06-08  15:24
 * @Description: 线程池配置
 * @Version: 1.0
 */
@Configuration
public class ThreadPoolConfig {
    // 核心线程池大小
    private int corePoolSize = 5;
    // 最大可创建的线程数
    private int maxPoolSize = 15;
    // 队列最大长度
    private int queueCapacity = 200;
    // 线程池维护线程所允许的空闲时间
    private int keepAliveSeconds = 300;

    @Bean(name = "threadPoolTaskExecutor")
    public ThreadPoolTaskExecutor threadPoolTaskExecutor()
    {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setMaxPoolSize(maxPoolSize);
        executor.setCorePoolSize(corePoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setKeepAliveSeconds(keepAliveSeconds);
        // 线程池对拒绝任务(无线程可用)的处理策略
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        return executor;
    }
}

调用类

package com.blockchain.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.blockchain.entity.*;
import com.blockchain.mapper.JobVacancyDao;
import com.blockchain.model.PageModel;
import com.blockchain.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.util.*;
import java.util.concurrent.CountDownLatch;
import java.util.stream.Collectors;

/**
 * (JobVacancy)表服务实现类
 *
 * @author kaysen
 * @since 2022-07-10 17:32:07
 */
@Service("JobTestService")
public class JobVacancyServiceImpl extends ServiceImpl<JobVacancyDao, JobVacancy> implements JobVacancyService {

    @Autowired
    private MemberService memberService;
    @Autowired
    private CompanyService companyService;
    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;


    @Override
    public List<JobVacancy> likeByJobName(String name, PageModel pageModel) {
        QueryWrapper<JobVacancy> jobQueryWrapper = new QueryWrapper<>();
        jobQueryWrapper.lambda().eq(JobVacancy::getStatus, 2)
                .eq(JobVacancy::getIsDelete, 1)
                .eq(JobVacancy::getPutOff, 1)
                .like(JobVacancy::getName, name);
        List<JobVacancy> list = this.list(jobQueryWrapper);
        getJobVacancyDetail(list);
        return list;
    }

    @Override
    public void getJobVacancyDetail(List<JobVacancy> list) {
        List<Integer> companyIds = new ArrayList<>();
        List<Integer> creatorIds = new ArrayList<>();
        for (JobVacancy jobVacancy : list) {
            if (!ObjectUtils.isEmpty(companyIds)) {
                if (!companyIds.contains(jobVacancy.getCompanyId())) {
                    companyIds.add(jobVacancy.getCompanyId());
                }
            } else {
                companyIds.add(jobVacancy.getCompanyId());
            }
            if (!ObjectUtils.isEmpty(creatorIds)) {
                if (!creatorIds.contains(jobVacancy.getCreator())) {
                    creatorIds.add(jobVacancy.getCreator());
                }
            } else {
                creatorIds.add(jobVacancy.getCreator());
            }
        }
        Map<Integer, Company> companyMap = new HashMap<>();
        CountDownLatch latch = new CountDownLatch(2);//减法计数器 相当于开启两个线程
        final String address="dddd";
        threadPoolTaskExecutor.execute(() -> {
            try {
                List<Company> companies = companyService.listByIds(companyIds);
                Map<Integer, Company> collect = companies.stream().collect(Collectors.toMap(Company::getId, p -> p));
                System.out.println(address);//局部参数传入线程需要用final修饰 避免使用时被修改
                companyMap.putAll(collect);//返回数据可以用map来承接 但hashMap有不安全的问题可用concurrentHashMap类,请看JUC-03 
            } finally {
                latch.countDown();//计数器自减1
            }
        });

        Map<Integer, Member> memberMap = new HashMap<>();
        threadPoolTaskExecutor.execute(() -> {
            try {
                List<Member> members = memberService.listByIds(creatorIds);
                Map<Integer, Member> collect = members.stream().collect(Collectors.toMap(Member::getId, p -> p));
                memberMap.putAll(collect);
            } finally {
                latch.countDown();//计数器自减1
            }
        });

        try {
            latch.await();//等计数器归零才执行一下方法
        } catch (InterruptedException e) {
            log.error("查询职位详情异常");
        }
        //组装数据
        for (JobVacancy jobVacancy : list) {
            jobVacancy.setCompany(companyMap.get(jobVacancy.getCompanyId()));
            jobVacancy.setCreatorMember(memberMap.get(jobVacancy.getCreator()));
        }
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值