Java并发编程:线程创建的三种方式详解
Java并发编程的核心在于对线程的有效管理。本文将深入探讨Java中创建线程的三种主要方式:继承Thread类、实现Runnable接口以及实现Callable接口。通过分析每种方式的代码结构、优缺点及适用场景,特别是针对获取返回值和多资源共享的需求,为开发者提供清晰的实践指南。
线程与进程的基础概念
理解线程创建之前,需明确进程与线程的关系。进程是程序在数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位。线程则是CPU调度和分配的基本单位,一个进程中至少包含一个线程,且线程间共享进程的资源。
- 动态性:进程作为运行中的程序,需要动态占用内存、CPU和网络等资源。
- 独立性:进程之间相互独立,拥有独立的内存区域。
- 并发性在单核CPU中,多个进程通过分时轮询切换快速交替执行,从而在宏观上呈现“同时执行”的效果。
方式一:继承 Thread 类
这是最直观的线程创建方式,适合简单的任务处理。其核心步骤包括定义继承Thread的类、重写run()方法,并通过调用start()启动线程。
第一步:定义线程类并继承Thread
创建一个新类MyThread,使其extends Thread。这种方式的优点是编码简单,在run()内部可直接使用this代表当前线程。但缺点是Java不支持多继承,一旦继承Thread,该类便无法再继承其他父类,限制了代码复用。
public class ThreadDemo {
public static void main(String[] args) {
// 3.创建一个线程对象
Thread t = new MyThread();
// 4.调用线程对象的start()方法启动线程
t.start();
for(int i = 0 ; i < 100 ; i++ ){
System.out.println("main线程输出:"+i);
}
}
}
// 1.定义一个线程类继承Thread类。
class MyThread extends Thread{
// 2.重写run()方法
@Override
public void run() {
// 线程的执行方法。
for(int i = 0 ; i < 100 ; i++ ){
System.out.println("子线程输出:"+i);
}
}
}第二步:启动线程
注意必须调用start()而非run()。直接调用run()只是普通方法调用,不会启动新线程。多线程环境下,由于CPU抢占执行,输出结果通常具有随机性。
方式二:实现 Runnable 接口(推荐)
为了解决继承Thread带来的单继承局限性,并实现任务代码与线程对象的解耦,推荐使用Runnable接口。这种方式允许线程任务对象被包装成多个线程,便于资源共享。
第一步:实现Runnable接口并重写run()
创建MyRunnable类实现Runnable接口。此时,MyRunnable只是一个普通的Java对象,不带有线程特性。
第二步:将任务包装为线程并启动
实例化MyRunnable后,将其作为参数传入Thread构造器。这种方式下,同一个Runnable对象可以被多次传给不同的Thread实例,从而实现多个线程共享同一份任务逻辑和资源。
public class ThreadDemo {
public static void main(String[] args) {
// 3.创建一个线程任务对象
Runnable target = new MyRunnable();
// 4.把线程任务对象包装成线程对象,可指定名称
Thread t = new Thread(target,"1号线程");
t.start();
// 同一个任务对象可以包装成另一个线程
Thread t2 = new Thread(target);
t2.start();
for(int i = 0 ; i < 10 ; i++ ){
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
}
}
// 1.创建一个线程任务类实现Runnable接口。
class MyRunnable implements Runnable{
// 2.重写run()方法
@Override
public void run() {
for(int i = 0 ; i < 10 ; i++ ){
System.out.println(Thread.currentThread().getName()+"==>"+i);
}
}
}优势总结:避免了单继承限制;实现了任务与执行的解耦;支持多线程共享同一资源。
方式三:实现 Callable 接口
与前两种方式最大的区别在于,Callable接口的call()方法可以有返回值,并且可以抛出受检异常。这在需要获取异步计算结果的场景中非常关键。
第一步:定义Callable任务并返回结果
创建MyCallable类实现Callable
第二步:通过FutureTask适配并启动线程
由于Thread构造器只接受Runnable,因此需要使用FutureTask作为中间桥接。FutureTask不仅实现了Runnable,还封装了Callable的回调机制,允许在线程结束后获取结果。
public class ThreadDemo {
public static void main(String[] args) {
// 3.创建一个Callable的线程任务对象
Callable call = new MyCallable();
// 4.将Callable任务包装为FutureTask
FutureTask task = new FutureTask<>(call);
// 5.把FutureTask对象包装成线程对象
Thread t = new Thread(task);
// 6.启动线程对象
t.start();
// ... 主线程执行其他逻辑 ...
for(int i = 1 ; i <= 10 ; i++ ){
System.out.println(Thread.currentThread().getName()+" => " + i);
}
// 获取执行结果,若未完成则阻塞等待
try {
String rs = task.get();
System.out.println(rs);
} catch (Exception e) {
e.printStackTrace();
}
}
}
// 1.创建Callable任务类
class MyCallable implements Callable{
// 2.重写call方法
@Override
public String call() throws Exception {
int sum = 0;
for(int i = 1 ; i <= 10 ; i++ ){
sum+=i;
}
return "执行的结果是:"+sum;
}
} 关键点:task.get()是一个阻塞方法,如果线程尚未执行完或执行完毕但未取值,该方法会一直等待直到获得结果。这是实现“带返回值的异步调用”的标准做法。
总结与选型建议
在实际开发中,优先推荐使用实现Runnable或Callable接口的方式,以保留类的继承灵活性并实现任务解耦。如果只需要执行无返回值的任务,选择Runnable;如果需要获取线程的执行结果,则选择Callable配合FutureTask。以上就是Java并发编程之线程创建的详细内容,更多关于Java线程高级应用资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









