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 来定义线程池,这样可以更灵活的控制


猜你喜欢

转载自blog.csdn.net/qq_46119575/article/details/131212257