线程的4种创建方式
1、继承Thread类
2、实现Runnable接口
3、实现Callable接口+FutureTask
4、线程池
方式一:继承Thread类
1、 编写Thread子类
// 继承Thread类
class MyThread extends Thread{
@Override
public void run() {
System.out.println("MyThread类方法执行了,线程名="+Thread.currentThread().getName()+",线程ID="+Thread.currentThread().getId());
}
}
2、使用
public static void main(String[] args) throws InterruptedException, ExecutionException {
System.out.println("方式1:继承Thread类=============================");
// 方式1:继承Thread,不能获取到调用结果
// 1、创建一个继承Thread的子类
MyThread myThread1 = new MyThread();
myThread1.setName("myThread1");
// 2、启动线程
myThread1.start();
System.out.println("main方法执行了,线程名="+Thread.currentThread().getName()+",线程ID="+Thread.currentThread().getId());
}
3、输出结果
方式1:继承Thread类=============================
main方法执行了,线程名=main,线程ID=1
MyThread类方法执行了,线程名=myThread1,线程ID=22
方式二:实现Runnable接口
1、 编写Thread子类
// 实现Runnable接口
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("MyRunnable类方法执行了,线程名="+Thread.currentThread().getName()+",线程ID="+Thread.currentThread().getId());
}
}
2、使用
public static void main(String[] args) throws InterruptedException, ExecutionException {
System.out.println("方式2:实现Runnable接口=============================");
// 方式2:实现Runnable接口,不能获取到调用结果
// 1、创建线程(通过Runnable接口的子类)
Thread myThread2 = new Thread(new MyRunnable());
myThread2.setName("myRunnable2");
// 2、启动线程
myThread2.start();
System.out.println("main方法执行了,线程名="+Thread.currentThread().getName()+",线程ID="+Thread.currentThread().getId());
}
3、输出结果
方式2:实现Runnable接口=============================
main方法执行了,线程名=main,线程ID=1
MyRunnable类方法执行了,线程名=myRunnable2,线程ID=23
方式三:实现Callable接口+FutureTask
1、编写Callable接口子类
// 执行正常
class MyCallable1 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("MyCallable类方法执行了,线程名="+Thread.currentThread().getName()+",线程ID="+Thread.currentThread().getId());
return 1;
}
}
// 执行异常
class MyCallable2 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
System.out.println("MyCallable类方法执行了,线程名="+Thread.currentThread().getName()+",线程ID="+Thread.currentThread().getId());
int i = 1/0;
return 1;
}
}
2、使用
public static void main(String[] args) throws InterruptedException, ExecutionException {
System.out.println("main方法执行了,线程名="+Thread.currentThread().getName()+",线程ID="+Thread.currentThread().getId());
// 方式3:实现 Callable接口+FutureTask,可以拿到返回结果,可以处理异常,其中Callable<T> T为返回值泛型
// 1、线程正常执行,获取返回值
// 1.1 创建线程(通过Callable接口的子类和FutureTask)
FutureTask<Integer> futureTask1 = new FutureTask<>(new ThreadCreateMethodClassTest.MyCallable1());
Thread myThread3 = new Thread(futureTask1);
myThread3.setName("myCallable3");
// 1.2 启动线程
myThread3.start();
Thread.sleep(3000);
// 1.3 获取返回值
System.out.println("打印执行结果:"+futureTask1.get());
// 2、线程异常执行,获取异常
// 2.1 创建线程(通过Callable接口的子类和FutureTask)
FutureTask<Integer> futureTask2 = new FutureTask<>(new ThreadCreateMethodClassTest.MyCallable2());
Thread myThread31 = new Thread(futureTask2);
myThread31.setName("myCallable3");
// 2.2 启动线程
myThread31.start();
// 2.3 获取异常
System.out.println("打印执行异常信息:"+futureTask2.get());
}
3、执行结果
方式3:实现 Callable 接口 + FutureTask=============================
MyCallable类方法执行了,线程名=myCallable3,线程ID=24
打印执行结果:1
MyCallable类方法执行了,线程名=myCallable3,线程ID=25
`Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at cn.study.zgm.thread01.threadcreate.ThreadCreateMethodClassTest.main(ThreadCreateMethodClassTest.java:41)
Caused by: java.lang.ArithmeticException: / by zero
at cn.study.zgm.thread01.threadcreate.ThreadCreateMethodClassTest$MyCallable2.call(ThreadCreateMethodClassTest.java:56)
at cn.study.zgm.thread01.threadcreate.ThreadCreateMethodClassTest$MyCallable2.call(ThreadCreateMethodClassTest.java:52)
at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
at java.util.concurrent.FutureTask.run(FutureTask.java)
at java.lang.Thread.run(Thread.java:750)`
方式四:使用线程池创建
ThreadPoolExecutor创建
Executors创建(也是通过ThreadPoolExecutor来进行封装创建的)
1、线程池创建
使用ThreadPoolExecutor构造器创建
方法参数介绍
corePoolSize:核心线程数(要保留在线程池中的线程数,即使它们处于空闲状态,除非设置了allowCoreThreadTimeOut)
corePoolSize:核心线程数(如果等于0,则任务执行完后,没有任务请求进入时销毁线程池中的线程。如果大于0,即使本地任务执行完毕,核心线程也不会被销毁。设置过大会浪费系统资源,设置过小导致线程频繁创建)
maximumPoolSize: 最大线程数(必须大于等于1,且大于等于corePoolSize。如果与corePoolSize相等,则线程池大小固定。如果大于corePoolSize,则最多创建maximumPoolSize个线程执行任务,其他任务加入到workQueue缓存队列中,当workQueue为空且执行任务数小于maximumPoolSize时,线程空闲时间超过keepAliveTime会被回收)
keepAliveTime: 线程空闲时间(线程池中线程空闲时间达到keepAliveTime值时,线程会被销毁,只到剩下corePoolSize个线程为止。默认情况下,线程池的最大线程数大于corePoolSize时,keepAliveTime才会起作用。如果allowCoreThreadTimeOut被设置为true,即使线程池的最大线程数等于corePoolSize,keepAliveTime也会起作用(回收超时的核心线程))
unit:单位(keepAliveTime单位)
workQueue: 工作队列(当请求线程数大于maximumPoolSize时,线程进入BlockingQueue阻塞队列)
threadFactory :线程工厂(用来生产一组相同任务的线程。主要用于设置生成的线程名词前缀、是否为守护线程以及优先级等。设置有意义的名称前缀有利于在进行虚拟机分析时,知道线程是由哪个线程工厂创建的)
handler:拒绝策略(执行拒绝策略对象。当达到任务缓存上限时(即超过workQueue参数能存储的任务数),执行拒绝策略,可以看做简单的限流保护)
拒绝策略介绍(RejectedExecutionHandler)
1、ThreadPoolExecutor.AbortPolicy: 【默认】丢弃任务并抛出RejectedExecutionException异常
2、ThreadPoolExecutor.DiscardPolicy:丢弃任务,但是不抛出异常
3、ThreadPoolExecutor.DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中
4、ThreadPoolExecutor.CallerRunsPolicy: 由调用线程(提交任务的线程)处理该任务,调用任务的run()方法绕过线程池直接执行
创建如下
// 通过线程池构造器创建
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 10, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(20), threadFactory, new ThreadPoolExecutor.AbortPolicy());
2、使用
线程池使用,分为无返回值/有返回值两种
public static void main(String[] args) {
List<Integer> numberList = new ArrayList<>(5);
for (int i = 0; i < 5; i++) {
numberList.add(i);
}
// 1、无返回值调度
System.out.println("ThreadPoolExecutor 无返回值=====================================");
ThreadPoolExecutor executor1 = new ThreadPoolExecutor(10, 100, 10, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(100));
try {
// 无返回值:execute
for (Integer number : numberList) {
executor1.execute(() -> {
System.out.println("打印参数:【"+number+"】,打印调度线程,线程名="+Thread.currentThread().getName()+",线程ID="+Thread.currentThread().getId());
});
}
}finally {
executor1.shutdown();
}
Thread.sleep(3000);
// 2、有返回值调度
System.out.println("ThreadPoolExecutor 有返回值=====================================");
ThreadPoolExecutor executor2 = new ThreadPoolExecutor(10, 100, 10, TimeUnit.MILLISECONDS, new LinkedBlockingDeque<>(100));
try {
// 有返回值:submit
LinkedBlockingDeque<Future<Integer>> blockingDeque = new LinkedBlockingDeque<>();
for (Integer number : numberList) {
Future<Integer> future = executor2.submit(() -> {
System.out.println("打印参数:【"+number+"】,打印调度线程,线程名="+Thread.currentThread().getName()+",线程ID="+Thread.currentThread().getId());
int i = 1/0;
return number*2;
});
blockingDeque.add(future);
}
List<Integer> resultList = new ArrayList<>(5);
while (!blockingDeque.isEmpty()){
Integer integer = blockingDeque.take().get();
resultList.add(integer);
}
System.out.println("输出返回值"+resultList.toString());
}finally {
executor2.shutdown();
}
}
3、执行结果
ThreadPoolExecutor 无返回值=====================================
打印参数:【0】,打印调度线程,线程名=pool-2-thread-1,线程ID=22
打印参数:【1】,打印调度线程,线程名=pool-2-thread-2,线程ID=23
打印参数:【2】,打印调度线程,线程名=pool-2-thread-3,线程ID=24
打印参数:【4】,打印调度线程,线程名=pool-2-thread-5,线程ID=26
打印参数:【3】,打印调度线程,线程名=pool-2-thread-4,线程ID=25
ThreadPoolExecutor 有返回值=====================================
打印参数:【2】,打印调度线程,线程名=pool-3-thread-3,线程ID=29
打印参数:【0】,打印调度线程,线程名=pool-3-thread-1,线程ID=27
打印参数:【3】,打印调度线程,线程名=pool-3-thread-4,线程ID=30
打印参数:【1】,打印调度线程,线程名=pool-3-thread-2,线程ID=28
打印参数:【4】,打印调度线程,线程名=pool-3-thread-5,线程ID=31
`Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
at java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.util.concurrent.FutureTask.get(FutureTask.java:192)
at cn.study.zgm.thread01.threadcreate.ThreadCreateMethodClassTest.main(ThreadCreateMethodClassTest.java:99)
Caused by: java.lang.ArithmeticException: / by zero
at cn.study.zgm.thread01.threadcreate.ThreadCreateMethodClassTest.lambda$main$1(ThreadCreateMethodClassTest.java:92)
at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
at java.util.concurrent.FutureTask.run(FutureTask.java)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:750)`
线程池探讨
线程池执行流程
1、线程初始化
根据用户给定参数创建一个线程池,参数包括核心线程数,最大线程数,存活时间以及队列。线程池初始化时,核心线程会被创建并保持活跃状态,而非核心线程处于非活跃状态。
2、任务放入线程池
当任务被放到线程池中时,线程池会首先检查当前是否有可用的线程,如果有,则将任务分配给该线程执行,如果没有则根据线程池的设置来处理该任务,这些设置有:
2.1 如果核心线程数未达到最大值,则新建线程处理任务;
2.2 如果核心线程数达到最大值,且队列可以容纳新的任务,则将任务放进队列;
2.3 如果核心线程数达到最大值,且队列满了,且总线程数未到最大值,则新建线程处理任务;
2.4 如果核心线程数达到最大值,队列满了,且总线程数已达到最大值,则拒绝任务(触发拒绝策略);
3、任务执行
当任务被分配给线程时,线程会调用任务的run方法,将任务执行完成。
4、任务结束
当任务执行完毕时,线程会退出,线程池会根据设置的线程来确定是保留线程还是将线程回收,以复用现有的线程资源。如果线程池中没有正在运行的线程,线程池会根据之前任务的处理情况,从队列中取出下一个任务,将其分配给现有线程或新建线程来执行
略进行处理
2、线程池问题验证
1、核心线程是在处理任务时才进行创建,而不是随线程池创建而创建。
验证代码
getPoolSize获取线程池中的线程数
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 1、验证核心线程在任务创建时进行创建,而不是随线程池创建而创建
LinkedBlockingQueue taskBlockQueue = new LinkedBlockingQueue<>(10);
ThreadPoolExecutor executor1 = new ThreadPoolExecutor(5, 10, 10, TimeUnit.MILLISECONDS,taskBlockQueue);
Thread.sleep(500);
System.out.println("打印线程池中核心线程数="+executor1.getPoolSize());
System.out.println("线程池处理任务=================================");
try {
LinkedBlockingDeque<Future<Long>> linkedBlockingDeque = new LinkedBlockingDeque<>();
for (int i = 0; i < 5; i++) {
Future<Long> submit = executor1.submit(() -> {
Thread.sleep(1);
return Thread.currentThread().getId();
});
linkedBlockingDeque.add(submit);
System.out.println("打印线程池中核心线程数="+executor1.getPoolSize());
}
while (!linkedBlockingDeque.isEmpty()){
linkedBlockingDeque.take().get();
}
} catch (Exception e) {
throw e;
} finally {
System.out.println("线程池资源释放前=================================");
System.out.println("打印线程池中核心线程数="+executor1.getPoolSize());
executor1.shutdown();
System.out.println("线程池资源释放后=================================");
}
Thread.sleep(1000);
System.out.println("打印线程池中核心线程数="+executor1.getPoolSize());
}
输出结果
打印线程池中核心线程数=0
线程池处理任务=================================
打印线程池中核心线程数=1
打印线程池中核心线程数=2
打印线程池中核心线程数=3
打印线程池中核心线程数=4
打印线程池中核心线程数=5
线程池资源释放前=================================
打印线程池中核心线程数=5
线程池资源释放后=================================
打印线程池中核心线程数=0
2、当核心线程数和任务队列已满时,会创建非核心线程,此时会将新任务(还未加入阻塞队列的任务)直接分配给非核心线程处理,而不是先处理阻塞队列中的队列,再将新任务添加到阻塞队列等待执行。
代码验证
设置任务执行睡眠时间,保证任务添加直到阻塞队列满时,还未有线程,防止执行完的线程对结果进行干扰。
核心线程数5,最大线程数10,阻塞队列大小为10
public static void main(String[] args) throws InterruptedException, ExecutionException {
ThreadFactory threadFactory = runnable -> {
Thread thread = new Thread(runnable);
thread.setName("[子线程]");
return thread;
};
LinkedBlockingDeque blockingDeque = new LinkedBlockingDeque<>(10);
ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 10, TimeUnit.MILLISECONDS,blockingDeque , threadFactory, new ThreadPoolExecutor.AbortPolicy());
try {
Set<Long> threadIdSet = new HashSet<>();
LinkedBlockingDeque<Future<Long>> linkedBlockingDeque = new LinkedBlockingDeque<>();
for (int i = 0; i < 20; i++) {
int number = i;
Thread.sleep(5);
Future<Long> submit = executor.submit(() -> {
Thread.sleep(400);
System.out.println("打印参数【" + number + "】,打印线程名=" + Thread.currentThread().getName() + ",打印线程ID=" + Thread.currentThread().getId());
return Thread.currentThread().getId();
});
linkedBlockingDeque.add(submit);
System.out.println("任务队列blockingDeque个数:"+blockingDeque.size());
}
while (!linkedBlockingDeque.isEmpty()){
Long aLong = linkedBlockingDeque.take().get();
threadIdSet.add(aLong);
}
System.out.println("线程ID个数:"+threadIdSet.size());
System.out.println(threadIdSet.toString());
} catch (Exception e) {
throw e;
} finally {
executor.shutdown();
}
}
输出结果
当任务数为20,结果如下:
任务队列blockingDeque个数:0
任务队列blockingDeque个数:0
任务队列blockingDeque个数:0
任务队列blockingDeque个数:0
任务队列blockingDeque个数:0
任务队列blockingDeque个数:1
任务队列blockingDeque个数:2
任务队列blockingDeque个数:3
任务队列blockingDeque个数:4
任务队列blockingDeque个数:5
任务队列blockingDeque个数:6
任务队列blockingDeque个数:7
任务队列blockingDeque个数:8
任务队列blockingDeque个数:9
任务队列blockingDeque个数:10
任务队列blockingDeque个数:10
任务队列blockingDeque个数:10
任务队列blockingDeque个数:10
任务队列blockingDeque个数:10
任务队列blockingDeque个数:10
打印参数【0】,打印线程名=[子线程],打印线程ID=22
打印参数【1】,打印线程名=[子线程],打印线程ID=23
打印参数【2】,打印线程名=[子线程],打印线程ID=24
打印参数【3】,打印线程名=[子线程],打印线程ID=25
打印参数【4】,打印线程名=[子线程],打印线程ID=26
打印参数【15】,打印线程名=[子线程],打印线程ID=27
打印参数【16】,打印线程名=[子线程],打印线程ID=28
打印参数【17】,打印线程名=[子线程],打印线程ID=29
打印参数【18】,打印线程名=[子线程],打印线程ID=30
打印参数【19】,打印线程名=[子线程],打印线程ID=31
打印参数【5】,打印线程名=[子线程],打印线程ID=22
打印参数【7】,打印线程名=[子线程],打印线程ID=24
打印参数【6】,打印线程名=[子线程],打印线程ID=23
打印参数【8】,打印线程名=[子线程],打印线程ID=25
打印参数【9】,打印线程名=[子线程],打印线程ID=26
打印参数【10】,打印线程名=[子线程],打印线程ID=27
打印参数【11】,打印线程名=[子线程],打印线程ID=28
打印参数【12】,打印线程名=[子线程],打印线程ID=29
打印参数【13】,打印线程名=[子线程],打印线程ID=30
打印参数【14】,打印线程名=[子线程],打印线程ID=31
线程ID个数:10
[22, 23, 24, 25, 26, 27, 28, 29, 30, 31]
当任务数大于20(此处验证任务数为21),(触发拒绝策略)结果如下:
任务队列blockingDeque个数:0
任务队列blockingDeque个数:0
任务队列blockingDeque个数:0
任务队列blockingDeque个数:0
任务队列blockingDeque个数:0
任务队列blockingDeque个数:1
任务队列blockingDeque个数:2
任务队列blockingDeque个数:3
任务队列blockingDeque个数:4
任务队列blockingDeque个数:5
任务队列blockingDeque个数:6
任务队列blockingDeque个数:7
任务队列blockingDeque个数:8
任务队列blockingDeque个数:9
任务队列blockingDeque个数:10
任务队列blockingDeque个数:10
任务队列blockingDeque个数:10
任务队列blockingDeque个数:10
任务队列blockingDeque个数:10
任务队列blockingDeque个数:10
`Exception in thread "main" java.util.concurrent.RejectedExecutionException: Task java.util.concurrent.FutureTask@47f6473 rejected from java.util.concurrent.ThreadPoolExecutor@6615435c[Running, pool size = 10, active threads = 10, queued tasks = 10, completed tasks = 0]
at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)
at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)
at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)
at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134)
at cn.study.zgm.thread01.threadcreate.ThreadPoolExecutorTest.main(ThreadPoolExecutorTest.java:56)`
打印参数【0】,打印线程名=[子线程],打印线程ID=22
打印参数【1】,打印线程名=[子线程],打印线程ID=23
打印参数【2】,打印线程名=[子线程],打印线程ID=24
打印参数【3】,打印线程名=[子线程],打印线程ID=25
打印参数【4】,打印线程名=[子线程],打印线程ID=26
打印参数【15】,打印线程名=[子线程],打印线程ID=27
打印参数【16】,打印线程名=[子线程],打印线程ID=28
打印参数【17】,打印线程名=[子线程],打印线程ID=29
打印参数【18】,打印线程名=[子线程],打印线程ID=30
打印参数【19】,打印线程名=[子线程],打印线程ID=31
打印参数【5】,打印线程名=[子线程],打印线程ID=22
打印参数【6】,打印线程名=[子线程],打印线程ID=23
打印参数【7】,打印线程名=[子线程],打印线程ID=24
打印参数【8】,打印线程名=[子线程],打印线程ID=25
打印参数【9】,打印线程名=[子线程],打印线程ID=26
打印参数【11】,打印线程名=[子线程],打印线程ID=28
打印参数【10】,打印线程名=[子线程],打印线程ID=27
打印参数【12】,打印线程名=[子线程],打印线程ID=29
打印参数【14】,打印线程名=[子线程],打印线程ID=31
打印参数【13】,打印线程名=[子线程],打印线程ID=30