Java多线程系列(一)——初识多线程
前言
无论学习哪种开发语言,多线程都是绕不开的一个知识点。在Java学习中,掌握牢固的多线程知识更是进阶高级程序员所必备的要求之一,本篇文章将围绕多线程的知识点进行讲解,并介绍多线程的含义,如何在Java中创建多线程以及线程的生命周期。希望这篇文章可以带领大家快速了解多线程的使用,想要了解更多关于Java多线程的内容,可以看本系列文章的其他内容。
一、初始多线程
(一)什么是线程
讲多线程之前,我们不妨先来理清三个概念:程序,进程和线程
什么是程序?
手机里下载的一个APP是一个程序,我们自己手写了一个简单的.java文件也是一个程序,可以这么理解:程序就是一种静态的文本文件,在特定的环境中可以实现特定的功能,也是我们俗称的代码
什么是进程?
进程可以理解为是程序的运行过程,比如我们运行了一个程序,那么对应的我们就在内存中产生了一个相关的进程出来,而程序和进程一般来说是一对多的关系,即一份程序可以通过多次执行来产生多个进程,可以同比电脑上一个QQ程序可以登录多个QQ。
那什么是线程呢?
线程是比进程更细小的一级划分,线程可以利用进程所拥有的资源,独立完成一项任务。在操作系统中,通常是把进程作为分配资源的基本单位,把线程作为独立运行和独立调度的基本单位。进程和线程也是一般是一对多或者一对一的关系,比如我们启动了电脑上的杀毒软件,它本身就是一个进程,但我们可以在软件上面同时执行垃圾回收和木马扫描等多个独立的功能,此时就是至少有2条线程在为我们进行服务。
在面试中常常会提问到进程和线程的区别,只要掌握好进程和线程之间的关系:线程依赖于进程存在,是系统调配的最小单位。就可以做到回答问题迎刃有余了。
(二)单线程和多线程
我们在前面介绍了一个进程中只要至少有一条线程就可以运行,如果进程中只有一条线程我们通常称之为单线程的进程,单线程的进程虽然可以满足我们一些简单的需求,比如计算数据等,但对于复杂的业务场景,单线程往往就无法满足我们的需求了。比如说我们玩超级马里奥游戏,除了有一条线程服务我们操作人物的移动,还会有线程来控制小兵的移动,控制背景音乐的开启等等,这些都是单线程所无法带来的好处。也可以这么理解单线程的运行是线性的,不支持多个不相关的程序同时运作,这也就降低了用户的体验。
多线程的优势可以总结为以下几点:
(1)给与用户更加丰富和快速的反馈,提升用户的体验
(2)可以更好地利用计算机的资源(特别是多核CPU),提高运行效率
二、Java线程的创建方法
在Java中,创建线程的方式有三种,分别是继承Thread
类、实现Runnable
接口和实现Callable
接口。
(一)继承Thread类
创建自定义线程类继承Thread类,在类中重写父类的run
方法,这里的话run方法将会实现输出0-99个数字
public class Thread01 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("start counting, now the num is "+i);
}
}
}
在测试类中进行测试
public class ThreadTest {
public static void main(String[] args) {
Thread01 thread01 = new Thread01();
System.out.println("strat the new thread...");
thread01.start();
System.out.println("the main thread stop running...");
}
}
我们在控制台上观察输出内容:

我们可以看到,main方法结束后,新的线程还在继续运行。
(二)实现Runnable接口创建线程
创建自定义类实现Runnable
接口,在类中实现run方法
public class Thread02 implements Runnable {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("start counting, now the num is "+i);
}
}
}
在测试类中进行测试,需要注意的是Thread02
类并没有start
方法,我们需要将该类对象作为参数传递给Thread类的构造方法中,从而生成对应的线程对象。
public class ThreadTest {
public static void main(String[] args) {
Thread thread = new Thread(new Thread02());
System.out.println("strat the new thread...");
thread.start();
System.out.println("the main thread stop running...");
}
}

我们可以看到,使用这种方式创建的线程效果和第一种一样
(三)实现Callable接口创建线程
创建自定义实现Callable
接口,重写父类的call()
方法
值得注意的是,call方法和之前的run方法不同,它是允许有返回值的
public class Thread03 implements Callable {
public String call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println("start counting, now the num is "+i);
}
return "success";
}
}
我们在测试类进行测试,我们需要将自定义的类作为参数传递给FutureTask
的构造函数中,再将FutureTask
对象传递给线程Thread
类的构造函数中,之后的步骤和前面两种方法一致,调用start方法即可。需要注意的是,我们可以在后面通过futureTask对象的get方法来获取到新线程中run方法的返回值。
public class ThreadTest {
public static void main(String[] args) {
FutureTask<String> futureTask = new FutureTask<String>(new Thread03());
Thread thread = new Thread(futureTask);
System.out.println("strat the new thread...");
thread.start();
try {
System.out.println(futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
System.out.println("the main thread stop running...");
}
}
三、线程的生命周期
线程的生命周期是一种动态的过程,在这个过程中线程的状态是不断发生变化的。了解线程生命周期之前,我们先来了解一下线程一共都有哪些状态。
(一)线程的状态
我们打开Thread
类的源码,可以发现里面有一个枚举类State
,里面就列举了所有的线程状态:
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
我们可以看到,线程的状态一共分为6种,分别是NEW
,RUNNABLE
,BLOCKED
,WAITING
,TIMED_WAITING
,TERMINATED
。这几种状态分别代表什么含义呢?
NEW
线程已创建,但还没有执行start方法启动
RUNNABLE
线程已经通过start方法启动,处于可运行的状态,只要得到操作系统的资源就可以运行(注意,哪怕线程处于Runnable状态,只要它没有被分配到操作系统资源,比如CPU,那么线程还是不能运行的)。
WAITING
线程处于等待状态,指的是该线程正在等待另一个线程执行某些特定的操作。比如说A线程调用了wait()
方法,进入了等待状态,那么此时只有其他线程执行notify()
或者notifyAll()
方法,才可以将线程的状态进行切换。同时,当A线程使用了B线程的join方法时,A线程也会进入waiting
状态。
TIMED_WAITING
调教时间等待,这种状态和WAITING
状态很像,区别在于这种状态一般都有设置指定的等待时长,当时间到达时就会自动唤醒线程。而WAITING
方法则无法自动唤醒。
BLOCKED
线程处于阻塞状态,是指该线程正在等待一个监控锁,特别是在多线程状态下等待另一个线程同步课的释放。比如,当A线程运行到同步代码块时,由于B线程还没有释放同步锁,所以A线程无法继续运行,进入了阻塞状态
TERMINATED
这种状态是线程执行完毕的状态。
我们可以通过下面的代码来简单理解一下线程的几种状态:
public class ThreadTest {
public static void main(String[] args) {
Thread thread = new Thread(()->{
for (int i = 0; i < 500; i++) {
System.out.println("start counting, now the num is "+i);
}
});
System.out.println("the thread has not started yet , now it's status is "+thread.getState());
thread.start();
System.out.println("the thread has started , now it's status is "+ thread.getState());
try {
Thread.currentThread().sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("one seconds have passed, now it's status is "+ thread.getState());
}
}


我们可以看见,线程的状态经历了从NEW -> RUNNABLE -> TEMINATED 的转变
(二)线程的生命周期
线程的生命周期自创建开始,而后执行start
方法之后变为RUNNABLE
状态,进入RUNNABLE
状态后,后续线程的状态就可能不断在BLOCKED
,WAITING
以及TIMED_WAITING
之间变换,直到线程结束进入TERMINATED
之后,才算是最终结束。

四、线程的优先级
当有多条线程同时运行时,系统会依据什么条件来判断哪些线程优先运行呢?
其中有一个判断条件就是线程的优先级,实际上,每条线程都有其优先级(默认是5),最大的优先级为10,优先级越高,就越容易优先获取CPU等系统资源。我们可以通过下面的案例来进行演示观察。
public class ThreadTest {
public static void main(String[] args) {
Runnable runnable = ()->{
int count = 0;
for (int i = 0; i < 10000; i++) {
count = count+i;
}
System.out.println(Thread.currentThread().getName()+" has finished, the count is "+count);
};
Thread thread1 = new Thread(runnable,"MY-THREAD-1");
thread1.setPriority(3);
Thread thread2 = new Thread(runnable,"MY-THREAD-2");
thread2.setPriority(7);
Thread thread3 = new Thread(runnable,"MY-THREAD-3");
thread3.setPriority(5);
Thread thread4 = new Thread(runnable,"MY-THREAD-4");
thread4.setPriority(10);
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
}

我们可以看到,此时最先完成任务的线程是线程1,然后是线程2,线程4和线程3。线程4虽然优先级别最高,但是执行的顺序比较靠后,所以实际上执行的起来可能没有其他线程这么早结束。同时由于各个电脑的配置不同,各位读者执行后的结果可能和我的也不相一致。
至此,对于多线程的入门就到此结束了,想要了解更多关于多线程的内容,可以看本系列的其他文章。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
· 上周热点回顾(3.3-3.9)