多线程:提升服务器性能的关键

多线程
引入
- 对于普通的家用电脑,进程数不会很多,但是对于服务器来说,不一定~
- 服务器要针对多个客户端的多种请求都要处理好,如果服务器是“串行执行”,就会使有些客户端等待很久
- 通过多线程的方式变成,就能够有效利用多个CPU核心
- 此方式在20年前比较流行,现在不怎么使用。缺陷:一个进程太“重”了
- 创建一个进程,比较重(消耗的资源比较多,时间比较长)
- 销毁一个进程,也比较重
客户端数目很多,但是每个客户端停留的时间就更短了,这两个额操作非常的频繁
- 线程的出现就是为了解决上述问题:
- 创建线程的开销要比创建进程小很多
- 销毁线程的开销比销毁进程小很多
- 多线程也能解决并发编程的问题(同时执行)
执行流
- 一个线程就是一个“执行流”
- 写了一个方法,方法中包含很多指令。所谓“执行流”就能执行上述指令,这个指令就可以放到cpu上执行
- 进程包含线程,进程中的每一个线程,可以共用这一份资源
- 有了线程之后,把原来进程的两个部分给拆分开来了:
- 进程负责资源分配
- 线程负责调度运行
例子:
![在这里插入图片描述]()
![在这里插入图片描述]()
![在这里插入图片描述]()
多个线程冲突
多个线程同时执行,一旦某个线程出现问题,就可能使整个进程被异常中止
- 🌟总结🌟
- 进程包含线程:一个进程中,可以用一个线程,也可以有多个线程,但是不能没有线程
- 进程是资源分配的基本单位;线程是调度执行的基本单位(进程专门负责资源分配,线程专门负责调度执行。因此,进程调度叫做线程调度更为准确)
- 每个进程都有自己独立的资源,一个进程的多个线程之间,共用一份资源
- 进程与进程之间,是“隔离”的,一个进程出问题,不容易影响到别的进程。同一个进程的线程和线程之间,是“共享资源”的,好处是让线程的创建节省资源申请的开销,让线程的销毁也节省资源释放的开销,坏处是容易冲突,一个线程出问题也容易把其他线程带走
- 因此,线程也成为“轻量级进程”,轻量体现在,创建销毁的开销更低,资源共享,省去了资源申请和释放的过程~
代码使用
- 线程是操作系统提供的概念,操作系统提供“线程操作”的api,一般由C/C++使用,Java标准库中有Thread类,通过这个类就封装了多线程的相关操作了。
在最简单的代码中:
public class Main {
public static void main(String[] args) {
System.out.println("Hello, World!");
}
}
- 这段代码中其实也有一个线程。点击运行创建Java进程,Java进程中就有一个线程,这个线程调用main方法
Java中Thread类的使用
- Java标准库中提供了Thread类,通过这个类就封装了多线程的相关操作
package Thread;
class MyThread extends Thread{
@Override
public void run() {
while (true){
System.out.println("hello thread");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Demo1{
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.start();
while(true){
System.out.println("hello world");
Thread.sleep(100);
}
}
}
- 多个线程的代码中,线程和线程之间是“并发执行”关系,执行逻辑的顺序,谁在前谁在后都有可能
- Thead.start()操作需要调用系统api创建线程,这个过程比较耗时,在线程创建的过程中,主线程就已经往下执行了。
![在这里插入图片描述]()

- 上面代码搞了两个线程执行
package Thread;
class MyThread extends Thread{
@Override
public void run() {
while (true){
System.out.println("hello thread");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
public class Demo1{
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
myThread.run();
while(true){
System.out.println("hello world");
Thread.sleep(100);
}
}
}
- 这种代码的写法,就不会创建新的进程。就一个main进程,然后进入run方法中的while循环就出不来了,无法打印“hello world”
![在这里插入图片描述]()
使用匿名内部类
基于Thread
package Thread;
public class Demo2 {
public static void main(String[] args) throws InterruptedException {
//子类的属性和方法
Thread t= new Thread(){
@Override
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 main");
Thread.sleep(1000);
}
}
}

- 创建Thread的匿名子类
- 重写run方法
- 创建子类的实例,赋值给t引用
- 此处定义的t并不是指向Thread的实例,而是指向了Thread的一个子类
基于Runnable
package Thread;
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable(){
@Override
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 main");
Thread.sleep(1000);
}
}
}

- 中间这一段都是Thread构造方法的参数
![在这里插入图片描述]()
代码等价于:
package Thread;
public class Demo3 {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable() {
@Override
public void run() {
while(true){
System.out.println("hello thread");
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
};
Thread t = new Thread(runnable);
t.start();
while(true){
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
基于lambda表达式
package Thread;
public class Demo5 {
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.start();
while (true){
System.out.println("hello main");
Thread.sleep(1000);
}
}
}
创建线程操作并命名
package Thread;
public class Demo6 {
public static void main(String[] args) {
Thread t1 = new Thread((Runnable) () -> {
while(true){
System.out.println("hello thread");
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "thread t");
t1.start();
}
}
守护线程、后台线程
package Thread;
public class Demo6 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread((Runnable) () -> {
while(true){
System.out.println("hello thread");
try{
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}, "thread t");
// 守护线程:当主线程结束时,守护线程也会结束、
//没有setDaemon(true),t1会一直运行(默认是前台进程)
// 而设置了setDaemon(true),t1会在主线程结束后结束(后台进程)
t1.setDaemon(true);
t1.start();
Thread.sleep(4000);
}
}

- 这里有两条线程,main方法执行完t1.start()如果没有sleep就结束了->main线程就结束了->main是这个进程唯一的前台线程->main的结束使整个进程结束->t进程结束
- 如果加上sleep,给t留下了更多的执行时间,此时sleep结束了,main才结束,t也就跟着结束了
要理解这个现象,需要结合守护线程的特性和主线程的生命周期来分析:
详细解释:
1. 先看“去掉Thread.sleep(4000);且保留t1.setDaemon(true)的情况”
此时代码逻辑是:
- 主线程启动守护线程
t1后,立刻结束(因为没有sleep阻塞,主线程执行完main方法就终止)。 - 守护线程(
t1)的生命周期依赖于“所有非守护线程(这里只有主线程)是否存活”:当所有非守护线程结束时,守护线程会被强制终止。
为什么只执行一次?
t1启动后,会先执行一次System.out.println("hello thread"),然后进入Thread.sleep(1000)休眠。- 在
t1休眠的1秒内,主线程已经执行完毕并终止(因为没有sleep阻塞)。 - 当主线程终止后,JVM发现“所有非守护线程都已结束”,会立即终止所有守护线程(包括正在休眠的
t1)。 - 因此
t1来不及执行第二次循环,看起来只执行了一次。
2. 再看“去掉Thread.sleep(4000);且去掉t1.setDaemon(true)的情况”
此时代码逻辑是:
t1是前台线程(默认非守护线程),其生命周期不依赖主线程,即使主线程结束,前台线程也会继续运行。- 主线程启动
t1后立刻结束,但t1作为前台线程会独立执行自己的while(true)循环,不受主线程影响。
为什么会无限执行?
- 前台线程的运行不依赖其他线程,只要自身的循环没结束(这里是
while(true)),就会一直执行。 - 因此即使主线程已经终止,
t1仍会持续打印“hello thread”,表现为无限执行。
区别
- 守护线程:依赖非守护线程存活,当所有非守护线程(如主线程)结束时,守护线程会被强制终止。
- 前台线程:独立于其他线程,即使主线程结束,前台线程也会继续执行直到自身结束。
去掉sleep后,主线程会快速结束,此时守护线程会被跟着终止(只执行一次),而前台线程会不受影响地无限循环。
isAlive是否存活
package Thread;
public class Demo7 {
public static void main(String[] args) throws InterruptedException {
Thread t= new Thread(() -> {
for (int i = 0; i < 3; i++) {
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
//获取t的存活状态
System.out.println(t.isAlive());
t.start();
Thread.sleep(1000);
System.out.println(t.isAlive());//运行中
// 等待t线程执行完毕
Thread.sleep(4000);
System.out.println(t.isAlive());
}
}

阻塞
package Thread;
import java.util.Scanner;
public class Demo9 {
private static boolean isRunning = true;
public static void main(String[] args) {
Thread t2 = new Thread(() -> {
//通过while来模拟执行很长的情况
while(isRunning){//变量捕获
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程执行完毕");
});
t2.start();
//在mian方法中,让用户通过进行输入,触发t线程的终止
Scanner sc = new Scanner(System.in);
System.out.println("请输入任意字符,触发线程终止");
sc.next();//使用这个方法,main就会阻塞,等待用户输入
isRunning = false;
}
}
- 变量捕获
private static boolean isRunning = true;要定义在main函数外边- 过程:这段代码的执行顺序如下,涉及主线程(
main线程)和子线程(t2线程)的并发执行:main 方法是静态方法,其中定义的变量是局部变量(仅在 main 方法内部可见)。而子线程 t2 的 Lambda 表达式属于另一个线程的执行体,它无法访问 main 方法的局部变量(除非该局部变量被 final 或 “事实上的 final” 修饰,但即使如此,也只能读取,无法修改后让子线程感知到变化)。
具体来说:
若 isRunning 定义在 main 方法内,子线程的 while(isRunning) 会直接报错(无法访问局部变量)。
即使通过 final 修饰让子线程可见,main 方法也无法修改它(final 变量不可变),失去了通过修改 isRunning 终止子线程的意义。
- 过程:这段代码的执行顺序如下,涉及主线程(
1. 程序启动,主线程开始执行
- 首先加载类
Demo9,初始化静态变量isRunning = true。 - 进入
main方法,开始主线程的执行流程。
2. 子线程t2的创建与启动
- 在
main方法中,创建线程t2,其任务是一个Lambda表达式(循环打印信息)。 - 调用
t2.start():启动子线程t2,此时t2进入就绪状态,等待CPU调度(不一定立即执行)。
3. 主线程继续执行,进入阻塞状态
t2启动后,主线程继续向下执行,创建Scanner对象,打印提示信息"请输入任意字符,触发线程终止"。- 执行
sc.next():主线程会阻塞在这里,等待用户从控制台输入内容(输入前,主线程暂停执行)。
4. 子线程t2的循环执行(与主线程并发)
- 当
t2被CPU调度后,开始执行其任务:- 进入
while(isRunning)循环(此时isRunning为true),不断打印"hello thread"。 - 每次打印后调用
Thread.sleep(1000),让t2休眠1秒(释放CPU资源,可能切换到其他线程)。 - 休眠结束后,重复循环,直到
isRunning变为false。
- 进入
5. 用户输入触发主线程唤醒,修改共享变量
- 当用户在控制台输入任意字符并回车后,
sc.next()返回,主线程从阻塞状态唤醒。 - 主线程执行
isRunning = false,修改静态变量isRunning的值。
6. 子线程t2终止循环,执行完毕
t2从休眠中唤醒后,再次判断while(isRunning)条件:- 此时
isRunning已被主线程改为false,循环终止。 - 子线程执行循环外的代码,打印
"线程执行完毕",随后t2线程结束。
- 此时
7. 程序结束
- 主线程在修改
isRunning后,若没有其他代码,会正常结束。 - 整个程序在所有线程(主线程和
t2)执行完毕后终止。
说明:
- 主线程和
t2线程是并发执行的,t2的循环打印与主线程的阻塞等待输入是同时进行的(宏观上)。 isRunning是共享的静态变量,主线程修改后,t2线程会读取到新值(但需注意:多线程下共享变量的可见性问题,此处因未加同步机制,理论上存在延迟可见的可能,但实际中大概率能正常终止)。
Interrupted
错误写法:
package Thread;
import java.util.Scanner;
public class Demo10 {
public static void main(String[] args) {
Thread t = new Thread(()->{
//通过isInterrupted()方法来判断线程是否被中断
while(t.isInterrupted()){
System.out.println("hello thread");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("线程执行完毕");
});
t.start();
System.out.println("输入任意字符,触发线程终止");
Scanner sc = new Scanner(System.in);
sc.next();
//终止t线程
t.interrupt();
}
}

原因:
new thread中执行的步骤:
- 定义lambda
- 把lambda作为Thread 构造方法参数
- Thread构造方法执行完,才初始化t
- 所以刚开始t变量并不存在
正确写法:
package Thread;
import java.util.Scanner;
public class Demo10 {
public static void main(String[] args) {
Thread t = new Thread(()->{
//通过isInterrupted()方法来判断线程是否被中断
while(!Thread.currentThread().isInterrupted()){
System.out.println("hello thread");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
System.out.println("线程执行完毕");
});
t.start();
System.out.println("输入任意字符,触发线程终止");
Scanner sc = new Scanner(System.in);
sc.next();
//终止t线程
t.interrupt();
}
}
- 使用Interrupt代替手动设置标志位的方式,触发线程终止
线程等待join
package Thread;
public class Demo11 {
private static int result = 0;
public static void main(String[] args) throws InterruptedException {
//让这个线程从1+2+3+...+100的和
//主线程中打印结果
//创建一个线程
Thread t = new Thread(()->{
int sum = 0;
for(int i=1;i<=100;i++){
sum += i;
}
result = sum;
System.out.println("子线程执行完毕,结果为:"+result);
});
t.start();
//
// Thread.sleep(1000);
t.join();
System.out.println(result);
}
}
public class Demo12 {
private static int result = 0;
public static void main(String[] args) {
Thread mainThread = Thread.currentThread();
//在main线程中计算1+2+3+...+100的和
Thread t1 = new Thread(()->{
try {
mainThread.join();
System.out.println("子线程执行完毕"+result);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
t1.start();
int sum = 0;
for(int i=1;i<=100;i++){
sum += i;
}
result = sum;
}
}







浙公网安备 33010602011771号