一,线程
进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。
线程是指进程中的一个执行流程,一个进程中可以运行多个线程。
1 创建线程的方法
1.1 继承Thread类
自定线程类并继承Thread类,重写run方法。创建线程对象,调用start()方法启动线程。
package mian.thread;
public class NewThread extends Thread{
public void run(){
for (int i = 0; i <100 ; i++) {
System.out.println("我还能卷"+i);
}
}
public static void main(String[] args) {
NewThread create = new NewThread();
create.start();
for (int i = 0; i < 100; i++) {
System.out.println("我不卷了"+i);
}
}
}
1.2 实现Runnable接口
自定义线程类,实现Runnable接口,并重写run方法。创建线程对象,调用start()方法启动线程。
package mian.thread;
public class newThread implements Runnable{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
System.out.println("继续刷leetcode"+i);
}
}
public static void main(String[] args) {
newThread thread = new newThread();
new Thread(thread).start();
for (int i = 0; i <100 ; i++) {
System.out.println("刷不死leetcode"+i);
}
}
}
1.3 实现Callable接口
实现步骤:
-
创建Callable接口的实现类,并重写call()方法,该call()方法将作为线程执行体,并且有返回值。
-
创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
-
使用FutureTask对象作为Thread对象的target创建并启动新线程。
-
调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
package mian.thread;
import java.util.concurrent.*;
public class NewThread2 implements Callable {
@Override
public Object call() throws Exception {
System.out.println("线程"+Thread.currentThread().getName());
return "实现callable"+Thread.currentThread().getName();
}
public static void main(String[] args) throws InterruptedException, TimeoutException, ExecutionException {
Callable newThread2 = new NewThread2();
FutureTask task1 = new FutureTask(newThread2);
FutureTask task2 = new FutureTask(newThread2);
new Thread(task1).start();
new Thread(task2).start();
Thread.sleep(100);
System.out.println(task1.get());
System.out.println(task2.get());
//get的传入参数为等待时间,超时抛出超时异常;传入参数为空时,则不设超时,一直等待
System.out.println(task1.get(10L, TimeUnit.MILLISECONDS));
System.out.println(task2.get(10L, TimeUnit.MILLISECONDS));
}
}
注:
1. Runnable和Callable
相同点:
1、两者都是接口
2、两者都需要调用Thread.start()启动线程
不同点:
1、如上面代码所示,callable的核心是call方法,允许返回值,runnable的核心是run方法,没有返回值。
2、call方法可以抛出异常,但是run方法不行
3、因为runnable是java1.1就有了,所以他不存在返回值,后期在java1.5进行了优化,就出现了callable,就有了返回值和抛异常
4、callable和runnable都可以应用于executors。而thread类只支持runnable
2. start()和run()的区别
start()方法用来开启线程,一个线程调用start()
方法后,此线程处于就绪(可运行)状态,只有得到cpu时间片,才开始执行run()方法。
而run()方法单独讲只是类的一个普通方法而已,如果直接调用Run方法,程序中只有主线程这一个线程,会把 run()
方法当成一个 main 线程下的普通方法去执行,没有达到多线程的目的。
总结: 调用start方法方可启动线程,而run方法只是thread的一个普通方法调用,还是在主线程里执行,把需要并行处理的代码放在run()方法中,start()方法启动线程将自动调用 run()方法。这是由jvm的内存机制规定的。并且run()方法必须是public访问权限,返回值类型为void。
二,线程池
线程池的实现类为ThreadPoolExecutor,其中构造方法有四种,代码如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
线程池的好处:
- 降低资源消耗。重复利用线程
- 提高响应速度。省略创建线程的步骤
- 提高线程的可管理性。由线程池统一分配,调优和监控
2.1线程池的重要参数
查看上面的代码,可以看到线程池的重要参数有如下几个:
-
corePoolSize
:核心线程最大数量,通俗点来讲就是,线程池中常驻线程的最大数量。线程池新建线程的时候,如果当前线程总数小于corePoolSize
,则新建的是核心线程;如果超过corePoolSize
,则新建的是非核心线程。 -
maximumPoolSize
:线程池中运行最大线程数(包括核心线程和非核心线程)。 -
keepAliveTime
:线程池中空闲线程(仅适用于非核心线程)所能存活的最长时间。当需要执行的任务很多,线程池的线程数大于核心池的大小时,keepAliveTime才起作用。 -
unit
:存活时间单位,与keepAliveTime搭配使用(TimeUnit.DAYS,TimeUnit.HOURS,TimeUnit.MINUTES,TimeUnit.MILLISECONDS,TimeUnit.MICRODECONDS) -
workQueue
:存放任务的阻塞队列。维护着等待执行的 Runnable对象,当所有的核心线程都在干活时,新添加的任务会被添加到这个队列中等待处理,如果队列满了,则新建非核心线程执行任务)。 -
handler
:线程池饱和策略。
2.1.1 线程池中的任务队列(workQueue)
线程池中的任务队列是基于阻塞队列实现的,即采用生产者消费者模式。Java 为我们提供了 7 种阻塞队列的实现,如下介绍:
- ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。
- LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列,在未指明容量时,容量默认为 Integer.MAX_VALUE。
- PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现 Comparable 接口也可以提供 Comparator 来对队列中的元素进行比较。跟时间没有任何关系,仅仅是按照优先级取任务。
- **DelayQueue:**类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列。要求元素都实现 Delayed 接口,通过执行时延从队列中提取任务,时间没到任务取不出来。
- SynchronousQueue: 一个不存储元素的阻塞队列,消费者线程调用 take() 方法的时候就会发生阻塞,直到有一个生产者线程生产了一个元素,消费者线程就可以拿到这个元素并返回;生产者线程调用 put() 方法的时候也会发生阻塞,直到有一个消费者线程消费了一个元素,生产者才会返回。
- LinkedBlockingDeque: 使用双向队列实现的有界双端阻塞队列。双端意味着可以像普通队列一样 FIFO(先进先出),也可以像栈一样 FILO(先进后出)。
- LinkedTransferQueue: 它是ConcurrentLinkedQueue、LinkedBlockingQueue 和 SynchronousQueue 的结合体,但是把它用在 ThreadPoolExecutor 中,和 LinkedBlockingQueue 行为一致,但是是无界的阻塞队列。
**注:**如果使用有界队列,当队列满了并且线程池数超过了最大线程数时就会执行拒绝策略;如果使用无界队列,那么任务队列永远都满不了,那么设置的最大线程数就没用意义了。
2.1.2 线程池中的拒绝策略(handler)
当线程池中,正在运行的线程数量达到了线程池中的最大线程数,并且任务队列也已经阻塞满了的时候,就会去执行拒绝策略,ThreadPoolTaskExecutor
中定义了如下策略:
- AbortPolicy(默认):抛出 RejectedExecutionException 异常,并丢弃新任务的处理。
- CallerRunsPolicy: 调用执行自己的线程运行任务,也就是直接在调用
execute
方法中的线程运行被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。 - DiscardPolicy:丢弃任务,但是不抛出异常。可以配合这种模式进行自定义的处理方式。
- DiscardOldestPolicy:丢弃任务队列中最早的未处理任务。
2.2 线程池的执行流程
下面用一张流程图来藐视一下线程池的工作原理,让自己和大家对线程池参数的作用有一个总体的把握。如下:
2.3 创建线程池
2.3.1 工具类 Executors封装好的线程池
1,SingleThreadExecutor(单线程池)
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。单线程池等同于传入参数为1的FixedThreadPool,代码如下:
package mian.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
*@Author wjq
*@Date 2022/5/21
*@Version v1.0
*@Description 单线程池
*/
public class createSingle {
public static void main(String[] args) {
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:" + Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:" + Thread.currentThread().getName());
}
});
}
}
查看线程池源代码:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
特点 :核心线程数和最大线程数相等都为1。线程没有存活时间,执行完任务就被回收,任务队列为LinkedBlockingQueue。其不适合并发操作。
2,FixedThreadPool(固定大小线程池)
需要指定线程池的大小,创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。代码如下:
package mian.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
*@Author wjq
*@Date 2022/5/20
*@Version v1.0
*@Description 定长线程池
*/
public class creatFixed {
public static void main(String[] args) throws InterruptedException {
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:" + Thread.currentThread().getName());
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:" + Thread.currentThread().getName());
}
});
}
}
查看线程池源代码:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
特点 :需要传入参数线程池中线程的个数nThreads,即核心线程数和最大线程数相等都为nThreads。线程没有存活时间,执行完任务就被回收,任务队列为LinkedBlockingQueue。
3,CachedThreadPool(可缓存线程池)
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。示例代码如下:
package mian.thread;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
*@Author wjq
*@Date 2022/5/20
*@Version v1.0
*@Description 可缓存线程池
*/
public class creatCached {
public static void main(String[] args) throws InterruptedException {
ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
for (int i = 0; i <100 ; i++) {
int index =i;
Thread.sleep(100);
cachedThreadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println("ThreadName:"+Thread.currentThread().getName()+",========index="+index);
}
});
}
}
}
线程池为无限大,当执行第二个任务时第一个任务已经完成,会复用执行第一个任务的线程,而不用每次新建线程。
查看线程池源码:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
特点:核心线程数为0,最大线程数为Integer.MAX_VALUE,线程存活时间为60L,任务队列为SynchronousQueue。
4,ScheduledThreadPool(定时线程池)
创建一个定长线程池,支持定时及周期性任务执行。代码如下:
package mian.thread;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
*@Author wjq
*@Date 2022/5/21
*@Version v1.0
*@Description 定长线程池
*/
public class createScheduled {
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
service.schedule(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:" + Thread.currentThread().getName());
}
}, 5, TimeUnit.SECONDS);
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("线程的名称:" + Thread.currentThread().getName());
}
}, 5, 2, TimeUnit.SECONDS);
}
}
线程池源代码如下:
private static final long DEFAULT_KEEPALIVE_MILLIS = 10L;
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue(), threadFactory);
}
特点:相比前面三种,顶时线程池的源码较为复杂,其核心线程数需要手动传入,最大线程数为
Integer.MAX_VALUE,线程存活时间为10L,任务队列为DelayedWorkQueue。
5,四种线程池对比
类型 | 线程类型 | 线程数量 | 适用场景 |
---|---|---|---|
SingleThreadExecutor(单线程池) | 核心线程 | core:1 max:1 | 单线程 |
FixedThreadPool(固定大小线程池) | 核心&非核心线程 | core:设定 max:设定 | 控制线程最大并发数 |
CachedThreadPool(可缓存线程池) | 非核心线程 | core:0 max:无限制 | 执行数量多,耗时少的任务 |
ScheduledThreadPool(定时线程池) | 核心&非核心线程 | core:设定max:无限制 | 定时/周期性任务 |
2.3.2 使用 ThreadPoolExecutor
构造函数自定义参数
自定义线程示例代码:
package mian.thread;
import java.util.Date;
/**
*@Author wjq
*@Date 2022/5/21
*@Version v1.0
*@Description 自定义线程
*/
public class DiyThread implements Runnable {
private String command;
public DiyThread(String s) {
this.command = s;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " Start. Time = " + new Date());
processCommand();
System.out.println(Thread.currentThread().getName() + " End. Time = " + new Date());
}
private void processCommand() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public String toString() {
return this.command;
}
}
自定义线程池示例代码:
package mian.thread;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
*@Author wjq
*@Date 2022/5/21
*@Version v1.0
*@Description 自定义线程池
*/
public class creatDiyThread {
private static final int core_pool_size=5;
private static final int max_poll_size =10;
private static final int queue_capacity=100;
private static final Long keep_alive_time=0L;
public static void main(String[] args) {
//通过ThreadPoolExecutor构造函数自定义参数创建
ThreadPoolExecutor myexecutor = new ThreadPoolExecutor(core_pool_size,
max_poll_size,
keep_alive_time,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(queue_capacity),
new ThreadPoolExecutor.DiscardOldestPolicy());
for (int i = 0; i <20 ; i++) {
Runnable worker = new DiyThread("" + i);
myexecutor.execute(worker);
}
myexecutor.shutdown();
while (!myexecutor.isTerminated()){
}
System.out.println("Finished all threads");
}
}
说明:我们自定义的线程池参数为:
corePoolSize | 5 |
---|---|
maximumPoolSize | 10 |
keepAliveTime | 0L(立即回收) |
unit | TimeUnit.SECONDS |
workQueue | 任务队列为 ArrayBlockingQueue ,并且容量为 100; |
handler | ThreadPoolExecutor.DiscardOldestPolicy |
总结:
- Executors 的 4 个功能线程池虽然方便,但现在已经不建议使用了,而是建议直接通过使用 ThreadPoolExecutor 的方式,这样的处理方式更加适用于实际需要场景,规避资源耗尽的风险。
- 由于可缓存线程池和定时线程池的最大线程数为Integer.MAX_VALUE,所以当大量任务一下涌入时,可能会一下子创建大量线程,造成OOM。