Java:创建线程、线程池有关内容汇总
前言
最近总结了有关Java线程的一些问题和解决方案,这里做一个分享和总结。
后期还会分享一些有关Java锁、Redis持久化等有关问题的总结内容,都是我自己从多个文档、视频总结出来的,希望对大家有用!
一、创建线程的4种方法
部分内容参考自:JUC多线程:创建线程的四种方式
1.继承Thread类
Thread 实现了 Runnable 接口,代表一个线程的实例。启动线程的唯一方法就是通过 Thread 类的start() 方法。start() 方法是一个native方法,它将启动一个新线程,并执行run()方法。
public class MyThread extends Thread {
public static void main(String[] args){
MyThread thread = new MyThread();
thread.start();
}
@Override
public void run() {
System.out.println("MyThread.run()");
}
}
优点:简单,继承 Thread 类即可
缺点:Java是单继承,继承了Thread类之后无法再继承别的类
重点:
(1)重写的是 run() 方法而不是 start()方法!
(2)Java单继承针对的是类与类之间的单继承,接口Interface是可以多继承的
2.实现Runnable接口
通过实现 Runnable 接口,实现 run() 方法,将 Runnable 接口的实现类的实例作为 Thread 的带参构造函数中,并通过调用 start() 方法启动线程
public class MyThread implements Runnable {
public static void main(String[] args){
Thread thread = new Thread(new MyThread());
thread.start();
}
public void run() {
System.out.println("MyThread.run()");
}
}
还可以通过匿名内部类的方式直接生成
public class MyThread {
public static void main(String[] args){
Thread thread = new Thread(new Runnable(){
public void run(){
System.out.println("Hello Runnable");
}
});
thread.start();
}
}
还可以通过lambda表达式的方式生成
public class MyThread {
public static void main(String[] args){
Thread thread = new Thread(() -> System.out.println("Hello lambda"));
thread.start();
}
}
Lambda表达式使用及详解:Java中Lambda表达式使用及详解
3.实现Callable接口
(1)实现 Callable 接口,并实现 call() 方法;
(2)创建 Callable 接口的实现类的实例,使用 FutureTask 类包装 Callable 对象,该 FutureTask 对象封装了 Callable 对象的 call() 方法的返回值;
(3)使用 FutureTask 对象作为 Thread 类的构造函数的 target 参数创建并启动线程;
(4)调用 FutureTask 对象的 get() 来获取子线程执行结束的返回值;
public class MyThread<String> implements Callable<String>{
public static void main(String[] args) throws Exception{
FutureTask<String> futureTask = new FutureTask<>(new MyThread());
Thread thread = new Thread(futureTask);
thread.start();
String result = futureTask.get();
System.out.println(result);
}
//重写call方法
@Override
public String call() {
return "Hello Callable";
}
}
4.线程池
用 ThreadPoolExecutor 创建线程池,并从线程池中获取线程用于执行任务。在 JUC 中,Executor 框架已经实现了几种线程池,我们就以 Executor 的 newFixedThreadPool 来作为 Demo
public class MyThread implements Runnable {
public static void main(String[] args) throws Exception{
ExcutorService executorService = Excutors.newFixedTthreadPool(10);
executor.execute(new MyThread);
}
public void run() {
System.out.println("MyThread.run()");
}
}
实现 Callable 或者 Runnable 接口都可以,由ExecutorService 来创建线程
线程池内容详解:ExecutorService详解
二、线程池状态
1.RUNNING
表示线程池正常运行,既能接受新任务,也会正常处理队列中的任务
2.SHUTDOWN
当调用线程池的 shutdown() 方法时,线程池就进入 SHUTDWON 状态,表示线程池处于正在关闭的状态,此状态下线程池不会接受新任务,但是会继续把队列中的任务处理完
3.STOP
当调用线程池的 shutdownnow() 方法时,线程池就进入 STOP 状态,表示线程池处于正在停止的状态,此状态下线程池既不会接受新任务,也不会处理队列中的任务,正在运行的线程也会被中断
4.TIDYING
线程池中没有线程在运行后,线程池的状态就会自动变为 TIDYING,并且会调用 terminated() ,该方法是空方法,留给程序员进行拓展
5.TERMINATED
terminated() 方法执行完之后,线程池状态就会变为 TERMINATED
三、为什么不建议使用Executors来创建线程池?
为了解释这个问题,我们可以来看一下 Excutors.newFixedTthreadPool() 的源代码:
//new LinkedBlockingQueue<Runnable>()这里可以看出 是声明的无界队列大小,默认大小为Integer.MAX_VALUE
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
问题的关键就在于: LinkedBlockingQueue(),这是一个无界阻塞队列,如果使用该线程池执行任务,如果任务过多就会不断添加到队列中,而任务越多占用的内存就越多,这就可能会导致内容占用的内存过多,导致出现OOM。尤其是对于规模大、复杂度高的系统,特别需要防范这一点
当我们用 Executors 创建 SingleThreadExecutor 时,对应的构造方法为:
//new LinkedBlockingQueue<Runnable>()这里可以看出 是声明的无界队列大小,默认大小为Integer.MAX_VALUE
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
同样是 LinkedBlockingQueue ,同样可能会耗尽内存
除了可能造成OOM之外,使用 Executors 创建线程池无法自定义线程的名字,不利于排查问题,所以建议直接用 ThreadPoolExecutor 来定义线程池,这样可以更灵活的控制