在JavaEE开发中,多线程是提升程序性能的核心手段之一。无论是处理高并发请求,还是优化IO密集型任务,理解线程的运作机制都至关重要。本文将从进程与线程的关系出发,深入剖析Java中创建线程的五种方式,并探讨线程生命周期中的关键方法与常见陷阱,帮助你构建扎实的并发编程基础。
一、进程与线程:并发编程的基石
在操作系统的世界里,进程是资源分配的基本单位,它拥有独立的内存空间,就像一栋大楼里的独立房间。而线程则是CPU调度的基本单位,它共享进程内的资源,如同在房间内工作的具体人员。当我们双击一个可执行文件(.exe),操作系统会为其创建一个进程,这个进程既包含资源容器(内存、文件句柄等),也承载着具体任务。

线程是进程内的执行路径,一个进程至少包含一个线程(如Java程序的主线程)。通过创建多个线程,我们可以实现并行处理,从而显著提升程序效率。例如,在处理网络请求时,为每个连接分配一个线程,可以避免因等待IO操作而阻塞整个程序。

二、多线程的权衡:效率与开销
虽然多线程能提升效率,但并非线程越多越好。⚠️ 过多的线程会导致上下文切换(Context Switch)开销增加——CPU需要频繁保存和恢复线程状态,同时资源竞争(如锁竞争)也会加剧,反而拖累整体性能。这就像在一个小房间里安排太多人工作,大家互相干扰,效率反而下降。
进程 | 线程 | |
资源占用 | 拥有独立资源,内存空间 | 共享进程资源 |
创建开销 | 大(分配独立资源) | 小(共享进程资源) |
稳定性 | 进程间相互独立互不影响 | 一个线程崩溃影响整个进程 |
上下文切换 | 代价大 | 代价小 |
理解上下文切换是掌握多线程性能的关键。当CPU从任务A切换到任务B时,需要保存A的寄存器状态、程序计数器等信息,再加载B的上下文。这一过程需要时间,因此线程数量应基于CPU核心数和任务特性(CPU密集型 vs IO密集型)合理设置,必要时可参考Go语言中的Goroutine设计理念——轻量级调度。
实践建议:对于CPU密集型任务,线程数通常设置为CPU核心数+1;对于IO密集型任务,可适当增加线程数以减少等待时间。
三、Java线程的创建:五种方式详解
Java中创建线程的方式灵活多样,每种方式都有其适用场景。以下是五种主流写法,从基础到进阶,建议全部掌握,以便在不同场景下选择最合适的方案。
1. 继承Thread类
直接继承Thread类并重写run()方法,这是最直观的方式。注意,start()方法会创建新线程并执行run(),而直接调用run()只会在当前线程中作为普通方法执行,不会并发运行。
class MyThread1 extends Thread{
//run方法是线程执行的入口,跟“main”方法效果差不多
@Override
public void run() {
while (true) {
System.out.println("hello run");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
MyThread1 t = new MyThread1();
t.start();
while (true) {
System.out.println("hello world");
Thread.sleep(1000);
}
}
}
2. 实现Runnable接口
实现Runnable接口并重写run()方法,然后将实例传给Thread构造器。这种方式更灵活,因为Java不支持多继承,但可以实现多个接口。
class MyRunnable1 implements Runnable{
@Override
public void run() {
while (true) {
System.out.println("hello run");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Demo2 {
public static void main(String[] args) throws InterruptedException {
MyRunnable1 myRunnable = new MyRunnable1();
Thread t = new Thread(myRunnable);
t.start();
while (true) {
System.out.println("hello world");
Thread.sleep(1000);
}
}
}
3. 匿名内部类(继承Thread)
如果线程逻辑只在某处使用一次,可以用匿名内部类简化代码。这种方式相当于省略了类定义和命名,直接创建实例。
class MyThread1 extends Thread{
//可以理解成自己写了Thread的"plus版本",加入了Thread原本没有的方法
@Override
public void run() {
}
}
}
public class Demo1 {
public static void main(String[] args) throws InterruptedException {
//创建你这个"plus版本"的实例
MyThread1 t = new MyThread1();
}
public class Demo3 {
public static void main(String[] args) {
//这里的t不是指向父类Thread,指向的是Thread的一个匿名子类
Thread t = new Thread(){
public void run(){
while (true){
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
t.start();
//主线程
while (true){
System.out.println("hello world");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
MyThread1 t = new MyThread1();
4. 匿名内部类(实现Runnable)
与第三种方式类似,但基于接口实现,省略的是实现类的定义。
public class Demo3 {
public static void main(String[] args) {
Thread t = new Thread(new Runnable(){
public void run(){
while (true){
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
});
t.start();
//主线程
while (true){
System.out.println("hello world");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
5. Lambda表达式(最推荐)
Lambda表达式是Java 8引入的语法糖,它本质上是Runnable接口的简洁实现。按住Ctrl点击Lambda表达式,可以看到底层就是run()方法。这种方式代码最简洁,可读性最高,是现代Java开发的首选。
public class Demo5 {
public static void main(String[] args) {
Thread t = new Thread(()-> {
while (true){
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
while (true){
System.out.println("hello world");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
对比参考:类似的语言如Python和JavaScript也提供了简洁的线程/异步语法(如Python的threading模块、JavaScript的async/await),但Java的线程模型更贴近操作系统底层,理解它有助于深入理解并发原理。
四、线程生命周期:关键方法与实战陷阱

1. 守护线程(Daemon Thread)与isAlive()
守护线程(通过setDaemon(true)设置)是“配角”线程,当所有前台线程结束时,后台线程会自动终止。例如垃圾回收线程就是守护线程。而isAlive()方法用于检查线程是否仍在运行。
package JavaEE;
public class Demo6 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()-> {
while (true) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
//在线程创建前设置(将它设置成后台线程)
t.setDaemon(true);
//线程创建
t.start();
//让main(唯一的前台线程)等会儿再结束
// 因为前台线程结束整个进程就会结束
Thread.sleep(3000);
}
}
一个线程 未执行/执行结束---------false
正在执行---------true
package JavaEE;
public class Demo7 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()-> {
for (int i = 0;i < 3;i++) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("hello Thread");
}
});
// 线程还未创建,因此是false
System.out.println(t.isAlive());
// 创建线程
t.start();
// 这个等待是让main这个前台线程执行的慢一些,
// 这样一来方便获取到正在执行的那个线程的存活状态
Thread.sleep(2000);
// 线程正在执行,因此是true
System.out.println(t.isAlive());
// 一个线程创建出来之后默认是前台线程
// 这个等待是为了让我们创建的那个先结束再观察存活状态
Thread.sleep(4000);
// 线程执行结束了,因此是false
System.out.println(t.isAlive());
}
}
2. 线程休眠与中断
sleep()方法让线程暂停执行指定的毫秒数。但要注意,sleep()不会释放锁,且如果被中断,会抛出InterruptedException。在循环中,若使用局部变量作为标志位,需确保变量是final或effectively final,否则会编译报错。
这里涉及到一个lambda变量捕获的点,涉及到lambda访问外部变量为什么需要"final/effectively final",必要的话可以具体搜一下
package JavaEE;
import java.util.Scanner;
public class Demo8 {
private static boolean a = true;
public static void main(String[] args) {
//创建一个局部变量a
//boolean a = true;
Thread t = new Thread(()-> {
while (a) {
System.out.println("hello world");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("thread结束");
});
t.start();
//作为局部变量它不能被修改,修改了就不满足effectively final了
//反过来说,如果局部变量没有被修改,idea会判定它是effectively final
//修改这个变量
//a = false;
Scanner scanner = new Scanner(System.in);
System.out.println("请随便输入一些东西来终止线程");
scanner.next();
//修改成功了!!!
//因为他是一个成员变量/(静态变量也可以)
a = false;
}
}
另外发现的点:假如在lambda内部尝试修改会出现什么结果?

线程中断(interrupt())提供了一种协作机制,让长时间运行或阻塞的线程能够响应中断请求。⚠️ 注意:interrupt()会设置中断标志位,但sleep()、wait()等方法在收到中断时会清空标志位并抛出异常,需在catch块中妥善处理。
.cyrrentThread是获取当前线程的对象
.isInterrupted()是检查是否设置了标志位interrupt
package JavaEE;
public class Demo9 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()-> {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("hello Thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//InterruptedException是一种通信机制,不是错误,使用RuntimeException会丢失这个信息
//throw new RuntimeException(e);
//直接终止
break;
}
}
System.out.println("线程结束");
});
t.start();
System.out.println("hello main");
Thread.sleep(1000);
//不仅起到设置标志位的作用,同时也起到中断阻塞的作用(这里是lambda里的sleep)
t.interrupt();
}
}
3. 线程等待(join)
join()方法让当前线程等待指定线程结束后再继续执行。这在需要汇总多个子线程结果时非常有用。使用时务必通过线程实例调用,而非Thread类静态调用。
线程都可以互相等待,这个没有什么优先级
实际开发中更倾向于在join()中加个上限参数避免死等
package JavaEE;
public class Demo10 {
private static int result = 0;
public static void main(String[] args) {
Thread t = new Thread(()->{
int sum = 0;
for (int i = 1; i <= 100 ; i++) {
sum += i;
}
result = sum;
});
t.start();
//可能会多余出来很多时间,时间不好把握
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
//这种处理看似可以,但不断进入循环也有开销
// while (result == 0) {
// try {
// Thread.sleep(1);
// } catch (InterruptedException e) {
// throw new RuntimeException(e);
// }
// }
try {
t.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//在main方法中打印结果
System.out.println(result);
}
}
这个是获取引用的写法,顺便展示了一下新线程等待主线程
package JavaEE;
public class Demo11 {
private static int result = 0;
public static void main(String[] args) {
//获取线程名称
Thread maintained = Thread.currentThread();
System.out.println("线程名称: "+ maintained.getName());
Thread t = new Thread(()->{
try {
maintained.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("计算结果: "+ result);
});
t.start();
int sum = 0;
for (int i = 1; i <= 100 ; i++) {
sum += i;
}
result = sum;
}
}

五、总结与最佳实践
多线程编程是JavaEE开发的必备技能,但也是一把双刃剑。掌握进程与线程的本质区别,理解上下文切换的开销,是设计高效并发系统的前提。在创建线程时,推荐优先使用Lambda表达式或Runnable接口,以保持代码的灵活性和可读性。同时,务必注意守护线程的生命周期、中断机制的协作性,以及join()的调用方式。
最后,建议在实际项目中结合线程池(如ExecutorService)来管理线程生命周期,避免频繁创建和销毁线程带来的性能损耗。类似地,Python的concurrent.futures和Go的Channel也提供了高级并发抽象,但理解底层的线程模型永远是进阶的关键。
希望本文能帮助你构建清晰的多线程知识框架。如果你觉得有用,欢迎分享给更多开发者,一起交流进步!
浙公网安备 33010602011771号