Java创建线程的4种方式(附带实例)
在 Java 中,创建线程的方式主要有 4 种,分别为:
下面我们详细介绍这 4 种方式及其区别。
代码如下:
代码如下:
Future 接口有几个方法可以控制关联的 Callable 任务对象。FutureTask 实现了 Future 接口,通过它的 get() 方法可以获取 Callable 任务对象的返回值。
代码如下:
示例代码如下:
它的缺点是代码更为复杂,需要进行更多的设计和考虑,比如线程池的大小选择、任务的提交与执行策略等。如果线程池使用不当或没有正确关闭,可能会导致资源泄漏。
综上所述,每种方式都有其用武之地,开发者需要根据具体场景选择适合的创建和启动线程的方式。简单任务通常只需要使用 Runnable 接口或 Thread 类,而复杂的并发程序可能会需要使用 Callable、Future 接口和线程池来提供更高级的并发管理功能。
- 继承 Thread 类;
- 实现 Runnable 接口;
- 使用 Callable 和 Future 接口;
- 使用线程池。
下面我们详细介绍这 4 种方式及其区别。
Java继承Thread类创建线程
当一个类继承自 Thread 类时,可以通过重写 run() 方法来定义线程执行的任务,然后通过创建该类的实例并调用 start() 方法来启动线程。代码如下:
class MyThread extends Thread { public void run() { // 线程执行的任务 } } MyThread t = new MyThread(); t.start();这种方式的优点是编码简单,能够直接使用;缺点是 Java 不支持多重继承,如果我们的类已经继承了另一个类,就不能使用这种方式创建线程。
Java实现Runnable接口创建线程
实现 Runnable 接口是创建线程的另一种方式。我们需要实现 run() 方法,然后将 Runnable 实例传递给 Thread 类的构造器,最后调用线程的 start() 方法。代码如下:
class MyRunnable implements Runnable { public void run() { // 线程执行的任务 } } Thread t = new Thread(new MyRunnable()); t.start();这种方式的优点是更灵活,允许我们的类继承其他类。同时,它也鼓励采用组合而非继承的设计原则,这使得代码更加灵活和易于维护。它的缺点是编程较复杂,需要构造 Thread 对象。
Java使用Callable和Future接口创建线程
Callable 和 Future 接口是一种更灵活的线程机制。Future 接口有几个方法可以控制关联的 Callable 任务对象。FutureTask 实现了 Future 接口,通过它的 get() 方法可以获取 Callable 任务对象的返回值。
代码如下:
FutureTask<Integer> futureTask=new FutureTask<Integer>( (Callable<Integer>)()-> { // 返回执行结果 return 123; } ); new Thread (futureTask,"返回值的线程").start(); try{ // 使用get()来获取Callable任务对象的返回值 System.out .println("Callable任务对象的返回值:"+futureTask.get()); }catch(Exception e) { e.printStackTrace(); }相比于实现 Runnable 接口方式,使用 Callable 和 Future 接口可以返回执行结果,也能抛出经过检查的异常。这种方式更加灵活,适用于复杂的并发任务。它的缺点是相对复杂,get() 方法在等待计算完成时是阻塞的。如果计算被延迟或永久挂起,调用者可能会长时间阻塞。
Java线程池创建线程
通过 Executors 的静态工厂方法获得 ExecutorService 实例,然后调用该实例的 execute(Runnable command) 方法即可使用线程池创建线程。一旦 Runnable 任务传递到 execute() 方法,该方法便会在线程池中选择一个已有空闲线程来执行任务,如果线程池中没有空闲线程便会创建一个新的线程来执行任务。示例代码如下:
public class Test4 { public static void main(String[] args) { ExecutorService executorService=Executors.newCachedThreadPool(); for (int i = 0; i < 5; i++){ executorService.execute(new MyTask()); System.out.println("************* a"+i+"*************"); } executorService.shutdown( ); } } class MyTask implements Runnable{ public void run( ){ System.out.println(Thread.currentThread().getName()+"线程被调用了。"); } }使用线程池方式的优点是能够自动管理线程的创建、执行和销毁,避免了创建大量线程引起的性能问题(因为频繁地创建和销毁线程会消耗大量系统资源),还能够限制系统中并发执行线程的数量,避免了大量并发线程消耗系统所有资源,导致系统崩溃。
它的缺点是代码更为复杂,需要进行更多的设计和考虑,比如线程池的大小选择、任务的提交与执行策略等。如果线程池使用不当或没有正确关闭,可能会导致资源泄漏。
总结
上述 4 种创建线程的方式都有其适用场景和优缺点:- 继承 Thread 类:简单直接,适用于简单的线程任务,不需要返回值,也不抛出异常,但在某些情况下因为Java的单继承限制而不够灵活;
- 实现 Runnable 接口:更加灵活,分离了线程的创建和任务的执行,符合面向对象的设计原则,适用于多个线程执行相同任务的场景,特别是当需要访问当前对象的成员变量和方法时;
- 使用 Callable 和 Future 接口:比实现 Runnable 接口复杂一些,使用也更复杂,但是提供了更强大的功能,适用于需要返回执行结果的多线程任务,或者需要处理线程中的异常的场景;
- 使用线程池:重用线程,减少创建和销毁线程的开销,并提供了控制最大并发线程数、调度、执行、监视、回收等一整套线程管理解决方案。
综上所述,每种方式都有其用武之地,开发者需要根据具体场景选择适合的创建和启动线程的方式。简单任务通常只需要使用 Runnable 接口或 Thread 类,而复杂的并发程序可能会需要使用 Callable、Future 接口和线程池来提供更高级的并发管理功能。