在 Java 中,实现多线程的主要有以下四种
继承 Thread 类,重写 run() 方法;
实现 Runnable 接口,实现 run() 方法,并将 Runnable 实现类的实例作为 Thread 构造函数的参数 target;
实现 Callable 接口,实现 call() 方法,然后通过 FutureTask 包装器来创建 Thread 线程;
通过 ThreadPoolExecutor 创建线程池,并从线程池中获取线程用于执行任务;
继承Thread类
创建步骤:
定义类继承Thread;
重写Thread类中的run方法;
实例化线程对象;
调用线程的start方法开启线程;
代码:
package com.scg.springcloudordercenter.controller;
// 1.继承Thread类
public class ThreadDemo extends Thread{
private int ticket=20;
// 2.重写run 方法
public void run(){
for(int i=0;i<20;i++){
if(this.ticket>0){
System.out.println("剩余票数=="+ticket--);
}
}
}
}
class ThreadTest {
public static void main(String[] args) {
// 3.实例化线程对象
ThreadDemo md = new ThreadDemo() ;
// 4.调用start()开启线程
md.start();
}
}
通过实现 Runnable 接口
创建步骤:
实现Runnable接口;
重写Run()方法;
实例化实现类;
将实例化线程类转化为线程对象;
开启线程 调用start()方法;
红色部分是与继承Thread类的方式不同的地方
代码
package com.scg.springcloudordercenter.controller;
// 1.实现Runnable类
public class RunnableDemo implements Runnable{
private int ticket=20;
// 2.重写run 方法
public void run(){
for(int i=0;i<20;i++){
if(this.ticket>0){
System.out.println("剩余票数=="+ticket--);
}
}
}
}
class RunnableTest {
public static void main(String[] args) {
// 3.实例化实现类
RunnableDemo rd = new RunnableDemo() ;
// 4.将实例化实现类转化为线程对象
Thread thread = new Thread(rd);
// 5.开启线程
thread.start();
}
}
实现 Runnable 接口比继承 Thread 类所具有的优势主要有:
① 可以避免 JAVA 中单继承的限制;
② 线程池只能放入实现 Runable 或 Callable类线程,不能直接放入继承 Thread 的类
③ 代码可以被多个线程共享,代码和数据独立,适合多个相同的程序代码的线程去处理同一个资源的情况
实现Callable接口
实现步骤
1、定义一个线程任务类实现Callable接口,声明线程执行的结果类型。
2、重写线程任务类的call()方法,这个方法可以直接返回执行的结果。
3、创建一个Callable的线程任务对象。
4、把Callable的线程任务对象包装成一个未来任务对象。
5、把未来任务对象包装成线程对象。
6、调用线程start()方法,启动线程。
7、获取线程执行结果。
代码:
/**
* @author gf
* @date 2023/2/22
*/
// 1、定义一个线程任务类实现Callable接口,声明线程执行的结果类型。
public class CallableTicket implements Callable<Object > {
private int ticket=20;
// 2、重写线程任务类的call()方法,这个方法可以直接返回执行的结果。
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1; i <= 100; i++) {
if (i % 2 == 0) {
sum += i;
}
}
return sum;
}
}
package com.scg.springcloudordercenter.controller;
import java.util.concurrent.FutureTask;
/**
* @author gf
* @date 2023/2/22
*/
public class CallableMain {
public static void main(String[] args) {
// 3、创建一个Callable的线程任务对象。
CallableTicket callableTicket = new CallableTicket();
// 4、把Callable的线程任务对象包装成一个未来任务对象。
FutureTask futureTask = new FutureTask(callableTicket);
// 5、把未来任务对象包装成线程对象。
Thread thread = new Thread(futureTask);
// 6、调用线程start()方法,启动线程。
thread.start();
// 7、获取线程执行结果。如果此时获取结果的任务还未执行完成,会让出CPU,直至任务执行完成才获取结果。
try {
//6.获取Callable中call方法的返回值
//get()返回值即为FutureTask构造器参数Callable实现类重写的call()方法返回值。
Object sum = futureTask.get();
System.out.println("总和为:"+sum);
} catch (Exception e) {
e.printStackTrace();
}
}
}
优点:
与使用Runnable相比,Callable功能更强大
相比run方法,可以有返回值。
方法可以抛异常。
支持泛型的返回值。
需要借助FutureTask类,比如获取返回结果。
Future接口
可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
FutureTask是Future接口的唯一实现类。
FutureTask同时实现了Runable,Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
使用线程池
通过Executors创建
它提供了四种线程池:
newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
public class Demo01 {
public static void main(String[] args) {
//ExecutorService threadPool = Executors.newSingleThreadExecutor(); //创建单个线程
//ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(5);//创建一个固定大小的线程池
//ExecutorService threadPool = Executors.newFixedThreadPool(5); //创建一个固定大小的线程池
ExecutorService threadPool = Executors.newCachedThreadPool(); //创建大小可伸缩的线程池
try {
for (int i = 0; i < 30; i++) {
//使用线程池来创建线程
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown(); //线程池使用完毕后需要关闭
}
}
}
注意:不建议使用这种方法创建线程池。因为newFixedThreadPool 和newSingleThreadExecutor允许的最大请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。newCachedThreadPoo和newScheduledThreadPool允许的创建线程的最大数量为Integer.MAX_VALUE,,从而导致OOM。
通过ThreadPoolExecutor创建
ThreadPoolExecutor有7个核心参数
ThreadPoolExecutor(int corePoolSize, //核心线程池大小,始终存在
int maximumPoolSize, //最大线程数
long keepAliveTime, //空闲线程等待时间,超时则销毁
TimeUnit unit, //时间单位
BlockingQueue<Runnable> workQueue, //等待阻塞队列
ThreadFactory threadFactory, //线程工厂
RejectedExecutionHandler handler) //线程拒绝策略
其最后一个参数拒绝策略共有四种:
new ThreadPoolExecutor.AbortPolicy():达到最大承载量,不再处理,并且抛出异常
new ThreadPoolExecutor.CallerRunsPolicy():达到最大承载量,从哪来的去哪里
new ThreadPoolExecutor.DiscardPolicy():达到最大承载量,丢掉任务,但不抛出异常
new ThreadPoolExecutor.DiscardOldestPolicy():达到最大承载量,尝试与最早执行的线程去竞争,不抛出异常
public class Demo02 {
public static void main(String[] args) {
//new ThreadPoolExecutor.AbortPolicy():达到最大承载量,不再处理,并且抛出异常
//new ThreadPoolExecutor.CallerRunsPolicy():达到最大承载量,从哪来的去哪里
//new ThreadPoolExecutor.DiscardPolicy():达到最大承载量,丢掉任务,但不抛出异常
//new ThreadPoolExecutor.DiscardOldestPolicy():达到最大承载量,尝试与最早执行的线程去竞争,不抛出异常
//最大线程池大小该如何定义
//1.cpu密集型,逻辑处理器个数
//2.io密集型 > 判断程序十分耗IO的线程,最大线程池大小应该比这个大
int maxPools= Runtime.getRuntime().availableProcessors();
System.out.println(maxPools);
ThreadPoolExecutor threadPool = new ThreadPoolExecutor(
2,
maxPools,
3,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy()
);
try {
for (int i = 0; i < 10; i++) {
//使用线程池来创建线程
//最大承载:maximumPoolSize+workQueue,超过执行拒绝策略
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+" ok");
});
}
} catch (Exception e) {
e.printStackTrace();
} finally {
threadPool.shutdown(); //线程池使用完毕后需要关闭
}
}
}
使用线程池的优点;
1.减少资源的消耗。重复利用已经创建的线程,避免频繁的创造和销毁线程,减少消耗。
2.提高响应速度。当执行任务时,不需要去创建线程再来执行,只要调动现有的线程来执行即可。
3.提高了线程的管理性。线程是稀缺资源,使用线程池可以进行统一的分配、调优和监控。