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

在这里插入图片描述

多线程

引入

  • 对于普通的家用电脑,进程数不会很多,但是对于服务器来说,不一定~
  • 服务器要针对多个客户端的多种请求都要处理好,如果服务器是“串行执行”,就会使有些客户端等待很久
  • 通过多线程的方式变成,就能够有效利用多个CPU核心
    • 此方式在20年前比较流行,现在不怎么使用。缺陷:一个进程太“重”了
    1. 创建一个进程,比较重(消耗的资源比较多,时间比较长)
    2. 销毁一个进程,也比较重
      客户端数目很多,但是每个客户端停留的时间就更短了,这两个额操作非常的频繁
  • 线程的出现就是为了解决上述问题:
    • 创建线程的开销要比创建进程小很多
    • 销毁线程的开销比销毁进程小很多
    • 多线程也能解决并发编程的问题(同时执行)

执行流

  • 一个线程就是一个“执行流”
    • 写了一个方法,方法中包含很多指令。所谓“执行流”就能执行上述指令,这个指令就可以放到cpu上执行
    • 进程包含线程,进程中的每一个线程,可以共用这一份资源
    • 有了线程之后,把原来进程的两个部分给拆分开来了:
      1. 进程负责资源分配
      2. 线程负责调度运行
        例子:
        在这里插入图片描述
        在这里插入图片描述
        在这里插入图片描述

多个线程冲突

多个线程同时执行,一旦某个线程出现问题,就可能使整个进程被异常中止

  • 🌟总结🌟
    1. 进程包含线程:一个进程中,可以用一个线程,也可以有多个线程,但是不能没有线程
    2. 进程是资源分配的基本单位;线程是调度执行的基本单位(进程专门负责资源分配,线程专门负责调度执行。因此,进程调度叫做线程调度更为准确)
    3. 每个进程都有自己独立的资源,一个进程的多个线程之间,共用一份资源
    4. 进程与进程之间,是“隔离”的,一个进程出问题,不容易影响到别的进程。同一个进程的线程和线程之间,是“共享资源”的,好处是让线程的创建节省资源申请的开销,让线程的销毁也节省资源释放的开销,坏处是容易冲突,一个线程出问题也容易把其他线程带走
    • 因此,线程也成为“轻量级进程”,轻量体现在,创建销毁的开销更低,资源共享,省去了资源申请和释放的过程~

代码使用

  • 线程是操作系统提供的概念,操作系统提供“线程操作”的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);
        }
    }
}

在这里插入图片描述

  1. 创建Thread的匿名子类
  2. 重写run方法
  3. 创建子类的实例,赋值给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)循环(此时isRunningtrue),不断打印"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中执行的步骤:

  1. 定义lambda
  2. 把lambda作为Thread 构造方法参数
  3. 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;

    }
}

posted @ 2025-11-16 14:46  dearbi  阅读(0)  评论(0)    收藏  举报  来源