Java多线程及线程池的使用
Java多线程
一、Java多线程涉及的包和类
-
java.lang包,主要是线程基础类
<1>Thread <2>Runnable <3>ThreadLocal
-
Java.util.concurrent包(JUC),主要是一些线程基础以及并发工具包。 提供了很多有用的类,方便我们进行并发程序的开发
有待更新!!!!!!!!!!!!!!!!!!!!!!!!!!!! <1>基础线程类 Callable、Future、FutureTask、CompletableFuture <2>容器类(线程安全) ArrayBlockQueue LinkedBlockQueue SynchronousQueue PriorityBlockingQueue DelayQueue ConcurrentHashMap CopyOnWriteArrayList CopyOnWriteArraySet <3>锁类(java.util.concurrent.locks) Condition Lock ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock ReadWriteLock ReentrantReadWriteLock <4>原子类(java.util.concurrent.atomic) AtomicBoolean AtomicInteger AtomicLong <5>多线程控制类 线程池:(接口)Executor 、ExecutorService (类)ThreadPoolExecutor、 ScheduledThreadPoolExecutor 线程池工具类: Executors 并发控制器:Semaphore(信号量)、CountDownLatch(倒数闩)、CyclicBarrier(同步屏障)、Exchanger(交换机)
二、Java创建多线程的方式
-
实现Runnable
-
继承Thread
-
实现Callable
-
线程池方式
-
java提供了构建线程池的工具,java.util.concurrent包中的Executors可以创建线程池。
但是 阿里编码规范中不允许使用这种方式构建线程池,这种方式对线程的控制粒度比较低。eg:线程名字不能改、线程数固定等
-
java手动创建线程池 ThreadPoolExecutor
new ThreadPoolExecutor(……)
-
Spring提供的构建线程池的工具 ThreadPoolTaskExecutor
-
三、Java线程池
1. 创建线程池ThreadPoolExecutor的7个参数
public ThreadPoolExecutor(int corePoolSize, //核心线程数
int maximumPoolSize, //最大线程数
long keepAliveTime, //最大空闲时间
TimeUnit unit, //时间单位
BlockingQueue<Runnable> workQueue, //阻塞队列
ThreadFactory threadFactory, //线程工厂
RejectedExecutionHandler handler) { //拒绝策略
……
}
- corePoolSize(必需):核心线程数。默认情况下,核心线程会一直存活,但是当将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
- maximumPoolSize(必需):线程池所能容纳的最大线程数。当活跃线程数达到该数值后,后续的新任务将会阻塞。
- keepAliveTime(必需):线程闲置超时时长。如果超过该时长,非核心线程就会被回收。如果将 allowCoreThreadTimeout 设置为 true 时,核心线程也会超时回收。
- unit(必需):指定 keepAliveTime 参数的时间单位。常用的有:TimeUnit.MILLISECONDS(毫秒)、TimeUnit.SECONDS(秒)、TimeUnit.MINUTES(分)。
- workQueue(必需):任务队列。通过线程池的 execute() 方法提交的 Runnable 对象将存储在该参数中。其采用阻塞队列实现。
- threadFactory(可选):线程工厂。用于指定为线程池创建新线程的方式,可指定线程池名字等。
- handler(可选):拒绝策略。当达到最大线程数时需要执行的饱和策略。
2. 线程池的执行流程
-
任务提交到线程池,若核心线程数有空闲,则执行;若核心线程数没有空闲,则任务要放到阻塞队列中。
-
若阻塞队列中没满,则放到队列中排队;若阻塞队列满了,则看工作线程数是否为最大线程数,
-
若没有达到最大线程数,则创建非核心线程数;若已经达到最大线程数,则采用拒接策略
线程池执行流程图
-
为什么要先进阻塞再去尝试创建非核心线程:
- 在创建新线程的时候,是要获取全局锁的,这时候其他线程会被阻塞,影响整体效率。
- 在核心线程已满时,如果任务继续增加那么放在队列中,等队列满了而任务还在增加那么就要创建临时线程了,这样代价低。
-
创建线程池时具体每个参数设多少值怎么定
-
创建线程池具体参数设置要看的变量有:每秒的任务数、每个任务花费的时间等等
3. 线程池的使用示例
1)用java原生的线程池类 ThreadPoolExecutor
/**
* ThreadPoolExecutor使用示例
* 1.构造一个线程池
* 2.往线程池中提交任务
* 3.关闭线程池
*
* @author zhangna
*/
public class ThreadPoolExecutorDemo {
private static int produceTaskSleepTime = 5;
private static int consumeTaskSleepTime = 5000;
private static int produceTaskMaxNumber = 20;
public static void main(String[] args) {
//1.构造一个线程池
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 4, 3,
TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3),
new ThreadPoolExecutor.DiscardPolicy());
try {
for (int i = 1; i <= produceTaskMaxNumber; i++) {
String work = "work@ " + i;
System.out.println("put :" + work);
//2.往线程池中提交任务
threadPool.execute(new ThreadPoolTask(work));
Thread.sleep(produceTaskSleepTime);
}
//3.关闭线程池
threadPool.shutdown();
threadPool.awaitTermination(1, TimeUnit.HOURS);
System.out.println("线程池中启动的任务已全部完成");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 线程池执行的任务
**/
public static class ThreadPoolTask implements Runnable, Serializable {
private static final long serialVersionUID = 0;
//保存任务所需要的数据
private Object threadPoolTaskData;
ThreadPoolTask(Object works) {
this.threadPoolTaskData = works;
}
@Override
public void run() {
//处理一个任务,这里的处理方式太简单了,仅仅是一个打印语句
System.out.println("start------" + threadPoolTaskData);
try {
//便于观察,等待一段时间
Thread.sleep(consumeTaskSleepTime);
System.out.println("end------" + threadPoolTaskData);
} catch (Exception e) {
e.printStackTrace();
}
threadPoolTaskData = null;
}
public Object getTask() {
return this.threadPoolTaskData;
}
}
}
2)用Spring推出的线程池工具 ThreadPoolTaskExecutor
-
配置一个线程池类,把线程池对象ThreadPoolTaskExecutor当作bean交给IOC容器,线程池叫taskExecutor
/** * 线程池配置 * * @author zhangna */ @Configuration @EnableAsync //开启对异步任务的支持 public class ThreadPoolConfig { /** * 配置了一个线程池,通过spring给我们提供的ThreadPoolTaskExecutor就可以使用线程池。 * 即把ThreadPoolTaskExecutor当作bean交给IOC管理,然后要使用线程池的时候,从IOC那拿ThreadPoolTaskExecutor即可使用线程池 * * @return */ @Bean("taskExecutor") public ThreadPoolTaskExecutor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); //设置核心线程数 executor.setCorePoolSize(5); //设置最大线程数 executor.setMaxPoolSize(20); //配置队列大小 executor.setQueueCapacity(Integer.MAX_VALUE); //设置线程活跃时间(秒) executor.setKeepAliveSeconds(60); //设置默认线程名前缀 executor.setThreadNamePrefix("blog_threadPool"); //等待所有任务结束后再关闭线程池 executor.setWaitForTasksToCompleteOnShutdown(true); //执行初始化 executor.initialize(); return executor; } }
-
定义一个执行异步任务的方法,并给该任务指定要执行它的线程池taskExecutor
@Component public class ThreadService { /** * 异步操作:更新阅读次数 * @Async 定义此方法为一个异步任务,使用自定义的线程池,即由@EnableAsync所标注的类下定义的线程池 */ @Async("taskExecutor") public void updateArticleViewCount(ArticleMapper articleMapper, Article article) { int viewCount = article.getViewCounts(); //进行articleMapper.update()时,有的值都要设置,所以为了最小限度的更改,就new一个新的Article只设置viewCount Article updateArticle = new Article(); updateArticle.setViewCounts(viewCount+1); LambdaUpdateWrapper<Article> updateWrapper = new LambdaUpdateWrapper<>(); updateWrapper.eq(Article::getId,article.getId()); //设置一个 为了在多线程的环境下 线程安全 updateWrapper.eq(Article::getViewCounts,viewCount); //update article set view_count=100 where view_count=99 and id=11; articleMapper.update(updateArticle,updateWrapper); } }
-
调用该异步方法
@Service public class ArticleServiceImpl implements ArticleService { @Autowired private ThreadService threadService; …… threadService.updateArticleViewCount(articleMapper, article); …… }
-
为何Spring要自己写一个ThreadPoolTaskExecutor并推荐代替直接使用ThreadPoolExecutor呢?
其实最主要的原因很直观:ThreadPoolExecutor是一个不受Spring管理生命周期、参数装配的Java类,而有了ThreadPoolTaskExecutor的封装,线程池才有Spring“内味”
-
@Async注解失效原因及解决方案(spring异步回调)
4. 获取多线程的运行结果—使用CompletableFuture
1)CompletableFuture概述
-
CompletableFuture是对Feature的增强,Feature只能处理简单的异步任务,而CompletableFuture可以将多个异步任务的结果进行复杂的组合
-
当异步方法有返回值时,如何获取异步方法执行的返回结果呢?
这时需要异步调用的方法带有返回值CompletableFuture
2)CompletableFuture使用示例如下
-
定义线程池
-
定义异步方法
@Async("taskExecutor") public CompletableFuture<String> doSomethingComp(String message) throws InterruptedException { log.info("do something1: {}", message); Thread.sleep(1000); return CompletableFuture.completedFuture("do something1: " + message); }
-
调用该异步方法,并对方法的返回值做一些操作,eg:可以3个方法并发调用,然后对结果进行合并处理,阻塞主线程;s1执行完成后并发执行s2和s3,然后消费相关结果,不阻塞主线程等等。可以对不同任务之间的结果做很多有用的处理
@SneakyThrows @GetMapping("/open/somethingComp") public String somethingComp() { int count = 10; CompletableFuture[] futures = new CompletableFuture[count]; // 开启待返回值得异步方法 for (int i = 0; i < count; i++) { futures[i] = asyncService.doSomethingComp("index = " + i); } try {// 等待所有任务都执行完 CompletableFuture.allOf(futures).join(); } catch (Exception e) { System.out.println("CompletableFuture error"); } System.out.println("Get all return value! "); return "success"; }
3)CompletableFuture进阶使用(结合lambda表达式)
过几天填坑