JAVA多线程五种创建方式
一、继承Thread类
Mythread类
/**
* @creater keke
* @time 2021/1/20 16:48
* @description
*/
public class MyThread extends Thread {
@Override
public void run() {
//通过继承Thread类的方式实现多线程时,可以直接使用this获取当前执行的线程
System.out.println("线程的名字是"+this.getName());//输出“线程的名字是Thread-0”
}
}
main方法
/**
* @creater keke
* @time 2021/1/20 12:44
* @description
*/
public class MyMain {
public static void main(String[] args) {
Thread thread = new MyThread();
//调用Thread类的currentThread()方法获取当前线程
System.out.println(Thread.currentThread().getName()); //输出“main”
//启动第一个线程
thread.start();
}
}
二、实现Runnable接口
MyRunnable类
/**
* @creater keke
* @time 2021/1/20 12:38
* @description
*/
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 10 ; i++) {
//当线程类实现Runnable接口时,获取当前线程对象只能通过Thread.currentThread()获取
System.out.println(Thread.currentThread().getName() + " " + i);//输出线程1 0-线程1 9
}
}
}
main方法
/**
* @creater keke
* @time 2021/1/20 12:44
* @description
*/
public class MyMain {
public static void main(String[] args) {
//调用Thread类的currentThread()方法获取当前线程
System.out.println(Thread.currentThread().getName());//输出”main“
MyRunnable myRunnable = new MyRunnable();
//通过new Thread(target,name)的方式创建线程:name:为线程设置名字
new Thread(myRunnable, "线程1").start();
}
}
- 实现Runnable接口的类的实例对象作为Thread对象的target,Runnable实现类里包含的run()方法作为线程执行体,实际的线程对象依然是Thread实例,负责执行其target的run()方法;
三、FutureTask
MyCallable实现Callable接口
import java.util.concurrent.Callable;
/**
* <h3>project2</h3>
* <p>实现Callable</p>
*
* @author : keke
* @date : 2021-01-20 19:41
*/
public class MyCallable implements Callable<String> {
@Override
public String call() {
System.out.println("通过实现Callable,线程名称:" + Thread.currentThread().getName());
return "这是个带返回的线程实现";
}
}
- Callable接口
1、Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大:call()方法可以有返回值,可以声明抛出异常。
2、Callable接口是一个带泛型的接口,泛型的类型就是线程返回值的类型。实现Callable接口中的call()方法,方法的返回类型与泛型的类型相同。
main方法
import java.util.concurrent.*;
/**
* <h3>project2</h3>
* <p>main方法</p>
*
* @author : keke
* @date : 2021-01-20 19:21
*/
public class MyMain {
public static void main(String[] args) throws InterruptedException, TimeoutException, ExecutionException {
Callable<String> callable=new MyCallable();
FutureTask<String> futureTask=new FutureTask<String>(callable);
new Thread(futureTask).start();
/* task.get()获取call()的返回值。若调用时call()方法未返回,则阻塞线程等待返回值
get的传入参数为等待时间,超时抛出超时异常;传入参数为空时,则不设超时,一直等待*/
System.out.println(futureTask.get());
System.out.println(futureTask.get(10, TimeUnit.MILLISECONDS));
}
}
-
FutureTask
1、使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。(FutureTask是一个包装器,它通过接受Callable来创建)。
2、Callable不是Runnable接口的子接口,所以Callable对象不能直接作为Thread对象的target,此时引入了RunnableFuture接口,RunnableFuture接口是Runnable接口和Future接口的子接口,可以作为Thread对象的target RunnableFuture接口的实现类:FutureTask同样也可以作为Thread对象的target。
3、调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。 -
FutureTask成员方法
取消该Future里面关联的Callable任务
boolean cancel(boolean mayInterruptIfRunning);
返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值
V get();
传入参数为等待时间,超时抛出超时异常抛出TimeoutException;传入参数为空时,则不设超时,一直等待
V get(long timeout,TimeUnit unit);
Callable任务完成,返回True,否则返回false
boolean isDone();
在Callable任务正常完成前被取消,返回True
boolean isCancelled();
四、使用线程池Executor框架
Executor接口
java.util.concurrent.包下.
- 优势
1、执行的业务逻辑Task与线程执行逻辑解耦。
2、通过框架控制线程的启动、执行、关闭、简化了编码流程,比起Thread.start();更简洁易管理。
3、线程池重复利用,新任务到来会优先使用空闲线程,找不到空闲线程则创建新线程。
4、避免this(当前对象)逃逸问题
void execute(Runnable command);
- 接收Runnable实例,执行实现Runnable接口的业务。
public interface ExecutorService extends Executor
- ExecutorService继承自Executor
Executors类
Executors类一系列工厂方法用于创建线程池,返回的线程池实现了ExecutorService接口。
- 可缓存线程池
(1)调用execute会优先重用先前构造的可用线程,如果没有可用线程,会创建新线程添加到池中。
(2)60秒未使用的线程将被终止并从中删除,因此,一个空闲时间足够长的池将不消耗任何资源
(3)可缓存线程池执行许多短期异步任务的程序,因为其重用线程会提高性能。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- 固定数目线程池
(1)该线程池也会优先重用,在队列中运行的固定数量的线程,在需要时使用提供的ThreadFactory创建新线程。
(2)在任何时候,至多{@code nThreads}个线程处于活动状态处理任务。
(3)当所有线程处于活动状态时,提交新任务挥会进入阻塞状态,将在队列中等待,直到有线程可用。
(4)如果任何线程在执行过程中由于失败而终止,那么如果需要执行后续任务,将有一个新线程代替它。
(5)线程池中的线程将会一直存在,直到它显式{@link ExecutorService#shutdown}。
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
- 单线程执行器
创建单线程执行器,该线程在执行过程中因失败而终止,将会有新线程执行后续操作
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
- 支持定时任务的线程
创建一个线程池,该线程池可以设置在给定时间后运行,或定期执行。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
ExecutorService提供的方法
void shutdown();
- 关闭自己,调用后停止接收任何新的任务,等待已经提交的任务都完成后关闭ExecutorService。
- 已经提交的任务包括:
1)正在执行的
2)未开始执行的
<T> Future<T> submit(Callable<T> task);
- 返回参数可跟踪一个或多个异步任务的执行情况
- 生命状态
1)运行:submit()创建后便进入运行状态
2)关闭:shutdown()后不再接受任何新的任务,但已经提交的任务还在进行
3)终止:所有已经提交的任务全部完成后即为终止状态
Executor执行Callable任务
/**
* @creater keke
* @time 2021/1/21 14:08
* @description
*/
@Slf4j
public class MyExecutor1 {
public static void main(String[] args) {
int size = 5;
List<Future<?>> futures = new ArrayList<Future<?>>();
ExecutorService executor = Executors.newCachedThreadPool();
for (int i = 0; i < size; i++) {
//使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中
Future<?> future = executor.submit(new MyCallable());
futures.add(future);
}
for (Future<?> fs : futures) {
try {
while(!fs.isDone());//Future返回如果没有完成,则一直循环等待,直到Future返回完成
System.out.println(fs.get()); //打印各个线程(任务)执行的结果
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
} finally {
//启动一次顺序关闭,不接受新任务,已提交的顺序执行完毕
executor.shutdown();
}
}
}
}
Executor执行Runnable任务
/**
* @creater keke
* @time 2021/1/21 14:08
* @description
*/
@Slf4j
public class MyExecutor2 {
public static void main(String[] args) {
int size = 10;
List<Future<?>> futures = new ArrayList<Future<?>>();
ExecutorService executor = Executors.newFixedThreadPool(size);
for (int i = 0; i < size; i++) {
executor.execute(new MyRunnable());
}
}
}
五、ThreadPoolExecutor自定义线程池
/**
* @creater keke
* @time 2021/1/20 12:44
* @description
*/
public class MyMain {
private static final int CORE_POOL_SIZE = 8;//线程池中所保存的线程数,包括空闲线程
private static final int MAX_POOL_SIZE = 16;//允许的最大线程数为16
private static final int QUEUE_CAPACITY = 1000; //任务执行前保存任务的队列,仅保存由execute方法提交的Runnable任务。
private static final Long KEEP_ALIVE_TIME = 1L;//当线程数大于核心数时,该参数为所有的任务终止前,多余的空闲线程等待新任务的最长时间。
public static void main(String[] args) {
ThreadPoolExecutor executor = new ThreadPoolExecutor(
CORE_POOL_SIZE,
MAX_POOL_SIZE,
KEEP_ALIVE_TIME,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(QUEUE_CAPACITY),
new ThreadPoolExecutor.CallerRunsPolicy());
Runnable runnable1 = new MyRunnable2();
Runnable runnable2 = new MyRunnable2();
Runnable runnable3 = new MyRunnable2();
Runnable runnable4 = new MyRunnable2();
Runnable runnable5 = new MyRunnable2();
Runnable runnable6 = new MyRunnable2();
Runnable runnable7 = new MyRunnable2();
executor.execute(runnable1);
executor.execute(runnable2);
executor.execute(runnable3);
executor.execute(runnable4);
executor.execute(runnable5);
executor.execute(runnable6);
executor.execute(runnable7);
//关闭线程池
executor.shutdown();
}
}
总结
创建线程的三种方式的对比
1.实现Runnable/Callable接口相比继承Thread类的优势
(1)实现多个线程执行同一个业务资源
(2)业务代码与线程代码分开,更好体现面向对象的模型
(3)Thread继承方式不建议使用, 因为Java是单继承的,继承了Thread就没办法继承其它类了,不够灵活。
2.Callable和Runnable的对比
(1) call()方法可以抛出异常,run()方法不可以
(2) 运行Callable任务可以拿到一个Future对象,可通过查询异步结果了解任务执行情况,可返回当前执行结果,也可取消任务的执行。
3.start()和run()的区别
(1) start()方法用来开启线程,但是线程开启后并没有立即执行,是按CPU分配的时间片来回切换的(当前线程(main)和创建的新线程),他需要获取cpu的执行权才可以执行。
(2) run()方法是由jvm创建完本地操作系统级线程后回调的方法,不可以手动调用。
4.实现Runnable和实现Callable接口的方式基本相同,因此可以把这两种方式归为一种这种方式,与线程池相比,使用线程池可以实现复用,项目开发中主要使用线程池。