一、线程的多种创建方式
在 Java 中,创建线程有多种方式,每种方式都有其特定的应用场景。以下是 Java 中常用的几种创建线程的方式,以及它们的具体实现:
1、通过继承 Thread
类创建线程
Java 中的 Thread
类提供了一个可执行的线程对象。通过继承 Thread
类并重写其 run()
方法来定义线程的执行体。
示例代码1:
java">class MyThread extends Thread {@Overridepublic void run() {// 线程要执行的代码System.out.println("Thread is running");}
}public class ThreadExample {public static void main(String[] args) {MyThread thread = new MyThread(); // 创建线程对象thread.start(); // 启动线程}
}
示例代码2:使用匿名内部类
java">public class AnonymousThreadExample {public static void main(String[] args) {// 创建线程并通过匿名内部类继承 Thread 类Thread thread = new Thread() {@Overridepublic void run() {// 线程要执行的代码System.out.println("Thread is running using Anonymous Thread");}};thread.start(); // 启动线程}
}
说明:
- 优点:简单直接,适用于简单的线程创建。
- 缺点:Java 中类只能继承一个类,因此如果已经继承了其他类,不能再继承
Thread
类。
2、通过实现 Runnable
接口创建线程
实现 Runnable
接口是一种更加灵活的方式,它可以避免 Java 单继承的限制。通过实现 Runnable
接口的 run()
方法来定义线程的执行体,再通过 Thread
类来启动该线程。
示例代码:
java">class MyRunnable implements Runnable {@Overridepublic void run() {// 线程要执行的代码System.out.println("Thread is running");}
}public class RunnableExample {public static void main(String[] args) {MyRunnable myRunnable = new MyRunnable(); // 创建 Runnable 实现类对象Thread thread = new Thread(myRunnable); // 将 Runnable 实现类传递给 Threadthread.start(); // 启动线程}
}
说明:
- 优点:比继承
Thread
更灵活,可以同时继承其他类。适用于多线程共享同一资源的情况。 - 缺点:需要通过
Thread
对象来启动线程,不能直接调用start()
方法。
3、通过实现 Callable
接口创建线程(可返回值)
Callable
接口与 Runnable
类似,但它允许任务执行时返回结果,可以处理异常。通常与 ExecutorService
配合使用,能够提交任务并获得执行结果。
示例代码:
java">import java.util.concurrent.*;class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {// 线程要执行的代码return "Thread is running and returning result";}
}public class CallableExample {public static void main(String[] args) throws ExecutionException, InterruptedException {MyCallable myCallable = new MyCallable();ExecutorService executorService = Executors.newCachedThreadPool(); // 创建线程池Future<String> future = executorService.submit(myCallable); // 提交任务String result = future.get(); // 获取任务的执行结果System.out.println(result); // 输出结果executorService.shutdown(); // 关闭线程池}
}
说明:
- 优点:能够返回任务执行结果,适用于需要计算并返回结果的场景。
- 缺点:需要使用
ExecutorService
来执行任务,相比Runnable
略显复杂。
4、通过 ExecutorService
创建线程池
线程池是一种管理线程的方式,通过 ExecutorService
可以创建线程池并提交任务,线程池会自动管理线程的创建、销毁和复用。通过线程池可以有效地管理多线程任务,避免频繁的线程创建销毁操作。
示例代码:
java">import java.util.concurrent.*;class MyTask implements Runnable {@Overridepublic void run() {System.out.println("Thread is running from ExecutorService");}
}public class ExecutorServiceExample {public static void main(String[] args) {ExecutorService executorService = Executors.newFixedThreadPool(3); // 创建固定大小的线程池for (int i = 0; i < 5; i++) {executorService.submit(new MyTask()); // 提交任务给线程池}executorService.shutdown(); // 关闭线程池}
}
说明:
- 优点:可以管理大量线程,自动复用线程,提高性能。适用于高并发的场景。
- 缺点:需要额外的线程池管理,使用稍微复杂。
5、通过 ForkJoinPool
创建线程(适用于分治任务)
ForkJoinPool
是一种专门用于处理大规模并行任务的线程池,特别适合分治型任务的执行。它通过将任务分割为多个子任务并行执行来提高性能。
示例代码:
java">import java.util.concurrent.*;class MyForkJoinTask extends RecursiveTask<Integer> {@Overrideprotected Integer compute() {// 线程要执行的代码return 1; // 示例返回值}
}public class ForkJoinPoolExample {public static void main(String[] args) {ForkJoinPool forkJoinPool = new ForkJoinPool(); // 创建 ForkJoinPoolMyForkJoinTask task = new MyForkJoinTask();ForkJoinTask<Integer> result = forkJoinPool.submit(task); // 提交任务System.out.println("Result: " + result.join()); // 获取结果forkJoinPool.shutdown(); // 关闭线程池}
}
说明:
- 优点:适合计算密集型任务和分治型任务,能够有效处理大量子任务。
- 缺点:相对于其他线程池,使用场景更为特殊。
6、通过 Lambda
表达式创建线程(简化代码)
Java 8 引入了 Lambda
表达式,可以简化线程的创建过程。通常与 Runnable
接口配合使用,能够在一行代码中定义线程的执行内容。
示例代码:
java">public class LambdaThreadExample {public static void main(String[] args) {// 使用 Lambda 表达式创建线程Thread thread = new Thread(() -> {System.out.println("Thread is running using Lambda");});thread.start(); // 启动线程}
}
说明:
- 优点:代码简洁,减少了冗长的类定义,适用于快速创建简单线程。
- 缺点:仅适用于
Runnable
接口场景,对于其他接口无法使用。
7、总结
Java 提供了多种方式来创建线程,每种方式都有其特定的优势和适用场景:
- 继承 Thread 类:适合简单的线程创建,但不能继承其他类。
- 实现 Runnable 接口:适合多线程共享资源的情况,比继承
Thread
更灵活。 - 实现 Callable 接口:适合需要返回结果的线程任务,通常与
ExecutorService
配合使用。 - 使用 ExecutorService:适合管理大量线程,自动复用线程,提高系统性能。
- 使用 ForkJoinPool:适合大规模并行计算和分治型任务。
- 使用 Lambda 表达式:适合快速创建简单线程,代码简洁。
选择哪种方式,取决于具体应用场景和任务需求。在实际开发中,通常推荐使用 ExecutorService
或 ForkJoinPool
来管理线程,以提高代码的可维护性和性能。
二、Thread类最常用个方法
1. start()
功能:启动一个新线程,使其进入就绪状态,等待操作系统调度执行。
- 作用:启动线程后,JVM 会自动调用
run()
方法,并在新的线程中执行。 - 注意:线程一旦启动后,不能再次启动。如果调用
start()
方法多次,会抛出IllegalThreadStateException
异常。
Thread thread = new Thread(() -> System.out.println("Thread is running"));
thread.start(); // 启动线程
2. run()
功能:线程的执行体方法,定义线程启动后要执行的代码。
- 作用:当调用
start()
启动线程时,JVM 会调用run()
方法。通常情况下,run()
方法会被覆盖,定义线程的具体执行逻辑。 - 注意:直接调用
run()
方法只是将run()
方法作为普通方法调用,并不会启动新的线程,仍然是在主线程中执行。
java">@Override
public void run() {System.out.println("Thread is running");
}
3. sleep(long millis)
功能:让当前线程暂停指定时间(以毫秒为单位),进入 休眠状态,不会占用 CPU 资源。
- 作用:使当前线程暂时进入休眠状态,休眠结束后,线程会回到就绪状态,等待操作系统调度。
- 注意:
sleep()
方法是静态的,作用于调用它的当前线程,且可以抛出InterruptedException
异常。
java">try {Thread.sleep(1000); // 当前线程暂停 1000 毫秒(1秒)
} catch (InterruptedException e) {e.printStackTrace();
}
4. join()
功能:使当前线程等待目标线程完成后再继续执行,常用于 线程同步。
- 作用:
join()
方法会使当前线程等待调用它的线程(即目标线程)执行完毕后再继续执行。 - 注意:可以指定等待时间,等待超时后
join()
方法会返回。
java">// 创建子线程对象
Thread thread = new Thread(() -> System.out.println("Thread is running"));
// 执行子线程
thread.start();
try {thread.join(); // 当前线程(即:主线程)等待子线程执行完
} catch (InterruptedException e) {e.printStackTrace();
}
5. interrupt()
功能:中断一个线程,设置线程的中断标志为 true
,通知线程终止。
- 作用:如果线程正在执行
sleep()
、wait()
或join()
等阻塞操作,interrupt()
会使这些操作抛出InterruptedException
,从而让线程终止或者退出阻塞。 - 注意:
interrupt()
只是设置线程的中断标志,不会立即停止线程的执行。线程需要在合适的地方自行判断Thread.interrupted()
或捕获InterruptedException
来响应中断。
java">Thread thread = new Thread(() -> {try {Thread.sleep(5000);} catch (InterruptedException e) {System.out.println("Thread was interrupted");}
});
thread.start();
thread.interrupt(); // 中断线程
6. isAlive()
功能:判断线程是否已经启动并正在执行。
- 作用:返回线程是否处于活动状态。如果线程处于 新建状态 或 已死亡状态,返回
false
;否则返回true
。
java">Thread thread = new Thread(() -> System.out.println("Thread is running"));
thread.start();
System.out.println(thread.isAlive()); // true
7. getName()
功能:获取线程的名称。
- 作用:返回当前线程的名字,通常是由 JVM 自动分配的,如
Thread-0
、Thread-1
等,也可以手动通过setName()
方法来设置。
java">Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName()));
thread.start(); // 输出:Thread-0
8. setName(String name)
功能:设置线程的名称。
- 作用:给当前线程指定一个名称。为线程设置名称有助于在调试和日志中跟踪线程。
java">Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getName()));
thread.setName("CustomThread");
thread.start(); // 输出:CustomThread
9. getId()
功能:获取线程的唯一标识符。
- 作用:返回当前线程的唯一 ID,通常用于调试和标识线程。
java">Thread thread = new Thread(() -> System.out.println(Thread.currentThread().getId()));
thread.start(); // 输出线程的 ID,例如:1
10. setPriority(int priority)
功能:设置线程的优先级,影响线程获取 CPU 时间片的频率。
- 作用:
setPriority()
方法允许你设置线程的优先级(从 1 到 10,默认值为 5)。高优先级线程通常会更早地获得 CPU 时间片,但这也依赖于操作系统的线程调度策略。
java">Thread thread = new Thread(() -> System.out.println("Thread is running"));
thread.setPriority(Thread.MAX_PRIORITY); // 设置为最高优先级
thread.start();
11. yield()
功能:使当前线程放弃 CPU 时间片,暂时让出 CPU 资源,允许其他线程执行。
- 作用:
yield()
是一个静态方法,当前线程会让出 CPU 使用权,但是不会完全让出控制权,仍然可以继续执行。具体行为依赖于操作系统的线程调度策略。
java">Thread.yield(); // 当前线程暂停,让出 CPU 资源
12. currentThread()
功能:获取当前正在执行的线程对象。
- 作用:返回代表当前正在执行线程的
Thread
对象。常用于获取当前线程的状态、名称等信息。
java">Thread current = Thread.currentThread();
System.out.println(current.getName()); // 输出当前线程的名称