并发是伴随着多核处理器的诞生而“席卷大地”的,为了充分利用硬件资源,诞生了多线程技术。但是多线程又存在资源竞争的问题,引发了同步和互斥的问题,JDK1.5推出的java.util.concurrent(简称JUC)并发工具包,就是专门来解决这些问题的。
new Thread的弊端:
-
new Thread新建对象,性能差。
-
线程缺乏统一的管理,可能无限制的新建线程,相互竞争,严重时会占用过多系统资源或OOM(内存溢出)。
ThreadPool线程池:
-
重用存在的线程,减少对象新建、消亡的开销。
-
线程总数可控,提高资源的利用率。
-
避免过多资源竞争,避免阻塞。
-
提供额外功能,定时执行、定期执行,监控等。
JUC提供了调度器对象Executors来创建线程池,可创建的线程池有四种:
①CachedThreadPool可缓存线程池
特点: 1.如果线程池中无空闲线程,则创建;有,则利用起来。
2.线程总数固定。
3.可以进行自动线程回收。
②FixedThreadPool定长线程池
特点: 1.如果线程池中有空闲线程,则利用起来;如果线程池中的所有线程都在执行任务,那么后续任务进入等待状态;
直到有空闲的线程了,该线程再执行后续任务。
2.无线程数上限。
注:如果有多个后续任务的话,那么空闲下来的线程会根据FIFO原则,来选择要执行哪一个线程。
(FIFO:即First Input First Output,先入先出;还有LIFI:即Last In First Out后入先出)
注:Java并发类型,可以分为两种:计算密集型(即:线程中大部分时间用来进行计算)和IO密集型(即:线程中大部
分时间用来进行IO)。如果是计算密集型的并发,那么线程池的(核心线程数)大小一般设置为N+1,如果是IO密
集型的并发,那么线程池的(核心线程数)大小一般设置为2N+1。其中N为电脑核数。但是在实际使用时,线程
池的大小往往没那么小,至于实际怎么设置线程池的大小,可参考这里。
③SingleThreadExecutor单线程线程池
说明: 用来模拟单线程的线程池
④ScheduledThreadPool调度线程池
说明: 一般用于处理定时任务;与其类似的还有Timer;在实际做项目时,我们既不用ScheduledThreadPool,
也不用Timer;而是用成熟的定时任务框架Quartz或Spring自带的定时调度。
注:Quartz或Spring定时调度的具体用法,可详见我的这篇博客
https://blog.csdn.net/justry_deng/article/details/80666508。
四种线程池的创建使用示例:
CachedThreadPool可缓存线程池,使用示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* CachedThreadPool的创建
*
* @author JustryDeng
* @date 2018/10/11 18:47
*/
public class ExecutorsCreateCachedThreadPool {
private static Integer count = 10000;
private static final Object OBJ= new Object();
public static void main(String[] args) throws InterruptedException {
// -> 创建可缓存线程池
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 1; i <= 10000; i++) {
// 使用lambda表达式简单实现Runnable接口的run方法
executorService.execute(() -> {
// 只有获得了object的锁的线程,才能操作
synchronized (OBJ) {
count--;
}
});
}
// 当线程池中所有线程都运行完毕后,关闭线程池
executorService.shutdown();
// 主线程阻塞2秒再输出count的值,为了避免输出打印count的值时,其余线程还没计算完;导致输出的不是count的最终值
Thread.sleep(2000);
System.out.println(count);
}
}
提示:在程序运行着的前提下,一个线程的消亡,(一般)不会影响到另一个线程的生命周期;本人的示例中,由
于main线程消亡后,程序就停止了运行,进而导致其他线程就无法完成自己完整的生命周期,所以本人
的示例中,为了不让程序过早停止运行,于是就主动让main线程sleep了一会儿。
在后面的博文中不再作相关解释。
注:其中Executors.newCachedThreadPool()方法的源码为:
FixedThreadPool定长线程池,使用示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* FixedThreadPool的创建
*
* @author JustryDeng
* @date 2018/10/11 18:47
*/
public class ExecutorsCreateFixedThreadPool {
private static Integer count = 10000;
private static Lock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
// -> 创建定长线程池(该线程池中总共存在200个线程)
ExecutorService executorService = Executors.newFixedThreadPool(200);
for (int i = 1; i <= 10000; i++) {
// 使用lambda表达式简单实现Runnable接口的run方法
executorService.execute(() -> {
// 使用重入锁,保证线程安全同步
lock.lock();
try {
count--;
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
});
}
// 当线程池中所有线程(包括排着队的)都运行完毕后,关闭线程池
executorService.shutdown();
// 主线程阻塞2秒再输出count的值,为了避免输出打印count的值时,其余线程还没计算完;导致输出的不是count的最终值
Thread.sleep(2000);
System.out.println(count);
}
}
注:其中Executors.newFixedThreadPool()方法的源码为:
SingleThreadExecutor单线程线程池,使用示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* SingleThreadExecutor的创建
*
* @author JustryDeng
* @date 2018/10/11 18:47
*/
public class ExecutorsCreateSingleThreadExecutor {
private static Integer count = 10000;
public static void main(String[] args) throws InterruptedException {
// -> 创建定单线程线程池
ExecutorService executorService = Executors.newSingleThreadExecutor();
for (int i = 1; i <= 10000; i++) {
// 使用lambda表达式简单实现Runnable接口的run方法
// 因为是单线程池,所以不需要任何其他操作,就能保证数据的安全准确性
executorService.execute(() -> count--);
}
// 当线程池中所有线程(包括排着队的)都运行完毕后,关闭线程池
executorService.shutdown();
// 主线程阻塞2秒再输出count的值,为了避免输出打印count的值时,其余线程还没计算完;导致输出的不是count的最终值
Thread.sleep(2000);
System.out.println(count);
}
}
注:其中Executors.newSingleThreadPool()方法的源码为:
ScheduledThreadPool调度线程池,使用示例:
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* ScheduledThreadPool的创建
*
* @author JustryDeng
* @date 2018/10/11 18:47
*/
public class ExecutorsCreateScheduledThreadPool {
public static void main(String[] args) {
// -> 创建定时调度线程池(初始化核心线程数为5)
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
// 使用lambda表达式简单实现Runnable接口的run方法
System.out.println("当前时间是:" + new Date());
scheduledExecutorService.schedule(() -> System.out.println("10秒后输出此语句! -> " + new Date()),10, TimeUnit.SECONDS);
// 当线程池中所有线程(包括排着队的)都运行完毕后,关闭线程池
scheduledExecutorService.shutdown();
}
}
注:其中Executors.newSingleThreadPool()方法的源码为:
在阿里piapia规范里,线程池不允许使用Executors去创建:
线程池不允许使用Executors去创建,而是通过主动创建ThreadPoolExecutor或ScheduledThreadPoolExecutor实例的方式去创建。这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
同时也给出了不推荐使用Executors创建线程池的原因:
◎newFixedThreadPool和newSingleThreadExecutor:
主要问题是堆积的请求处理队列可能会耗费非常大的内存,甚至OOM。
◎newCachedThreadPool和newScheduledThreadPool:
主要问题是线程数最大数是Integer.MAX_VALUE,可能会创建数量非常多的线程,甚至OOM。
◎让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
阿里推荐用以下方式来创建线程池:
声明:构造方法的各参数介绍,可详见https://blog.csdn.net/justry_deng/article/details/89331199。
创建调度线程池ScheduledExecutorService:
需要引入依赖:
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>
提示:SpringCloud项目,zuul等模块儿本身就依赖有commons-lang3依赖。
注:如果不想引入commons-lang3依赖,那么可以使用默认的线程工厂或者其它的线程工厂实现。
创建:
// 需要引入commons-lang3依赖
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1, new BasicThreadFactory
.Builder()
.namingPattern("example-schedule-pool-%d")
.daemon(true)
.build());
创建公共线程池ExecutorService:
需要引入依赖:
<!-- https://mvnrepository.com/artifact/com.google.guava/guava -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>27.0.1-jre</version>
</dependency>
提示:SpringCloud项目,eureka、zuul等模块儿本身就依赖有guava依赖。
注:如果不想引入guava依赖,那么可以使用默认的线程工厂或者其它的线程工厂实现。
创建:
// ThreadFactoryBuilder需要引入guava依赖
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("demo-pool-%d").build();
// 创建Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(5, 50, 3000L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(20),
namedThreadFactory,
new ThreadPoolExecutor.AbortPolicy());
// 执行任务
pool.execute(()-> System.out.println(Thread.currentThread().getName()));
pool.shutdown();//gracefully shutdown
使用示例:
/**
* 虽然我们可以通过Excutors创建线程池,但是推荐:我们自己手动创建线程池
*
* @author JustryDeng
* @date 2018/12/29 13:58
*/
public class CreateThreadPool {
private static final Object OBJ = new Object();
private static Integer count = 10000;
public static void main(String[] args) {
try {
createThreadPoolTest();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void createThreadPoolTest() throws InterruptedException {
int length = 10000;
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("justry-deng-pool-%d").build();
ExecutorService executorService = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(1024),
namedThreadFactory,
new ThreadPoolExecutor.AbortPolicy());
for (int i = 1; i <= length; i++) {
// 使用lambel表达式简单实现Runnable接口的run方法
executorService.execute(() -> {
// 只有获得了object的锁的线程,才能操作
synchronized (OBJ) {
System.out.println(Thread.currentThread().getName());
count--;
}
});
}
// 当线程池中所有线程都运行完毕后,关闭线程池
executorService.shutdown();
// 主线程阻塞2秒再输出count的值,为了避免输出打印count的值时,其余线程还没计算完;导致输出的不是count的最终值
Thread.sleep(2000);
System.out.println(count);
}
}
直接在spring配置文件中配置线程池:
<bean id="userThreadPool"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="10" />
<property name="maxPoolSize" value="100" />
<property name="queueCapacity" value="2000" />
<property name="threadFactory" value= threadFactory />
<property name="rejectedExecutionHandler">
<ref local="rejectedExecutionHandler" />
</property>
</bean>