JAVA多线程

发布于:2025-03-26 ⋅ 阅读:(27) ⋅ 点赞:(0)

目录

一、多线程概念(进程和线程的区别和联系):

1.核心区别:

2.核心联系:

3.类比理解:

二、多线程创建的三种方式(第三种常用):

1. 继承 Thread 类:

2.实现 Runnable 接口:

3. 基于Lambda 表达式创建线程(推荐):

三、线程的几种属性:

四、线程的属性执行过程详解:

1、启动线程:

2、获取线程对象引用:

3、中断一个线程:

1、通过变量中断一个线程:

2、直接使用线程内置的标志位 isinterruptted()

4、等待一个线程:

五、线程的状态:


一、多线程概念(进程和线程的区别和联系):

 线程 与 进程 的区别

进程:程序的一次动态执行过程,拥有独立的内存空间和资源,是操作系统资源分配的基本单位

线程:进程内的执行单元,共享进程的资源(如内存),是CPU调度的基本单位。一个进程可包含多个线程,实现任务并发执行。

并发 与 并行

并发:多个线程交替执行(单核 CPU 通过时间片切换)。 CPU 计算能力强大,每秒可以计算几十亿次,但实际上在某一时刻,CPU 只能执行一个指令。并发是通过 CPU 在多个任务之间快速切换来实现的。由于切换速度极快,从宏观上看,这些任务就像是在同时执行的。

并行:多个线程同时执行(多核 CPU 支持)。

1.核心区别:

特性 进程 线程
定义 操作系统资源分配的基本单位,独立运行的程序实例。 进程内的执行单元,共享进程资源,是CPU调度的基本单位。
内存空间 拥有独立的内存空间(堆、代码段、数据段等)。 共享所属进程的内存空间,但每个线程有自己的栈和寄存器。
独立性 进程间相互隔离,一个进程崩溃不影响其他进程。 线程间共享资源,一个线程崩溃可能导致整个进程终止。
创建/销毁开销 开销大(需分配独立资源)。 开销小(共享进程资源)。
上下文切换 切换速度慢(需切换内存空间、文件描述符等)。 切换速度快(仅切换栈、寄存器等少量资源)。
资源占用 占用内存多(独立地址空间)。 占用内存少(共享地址空间)。

2.核心联系:

1.包含关系:

        线程是进程的子集,一个进程至少包含一个主线程(比如main方法),可以创建多个线程。

        多个线程共享进程的资源(如堆内存、文件句柄、全局变量等)。

2.协作目标:

        两者都用于实现并发执行,提高程序效率。

        进程用于隔离不同任务(如浏览器多标签页),线程用于高效率利用资源(如并行计算)。

3.依赖关系:

        线程必须依附于进程存在,进程终止时所有线程也会终止。

3.类比理解:

进程:像工厂,拥有独立的厂房(内存)、设备(资源)、和工人(线程)。

线程:工厂内的工人,共享厂房和设备,协作完成生产任务。

二、多线程创建的三种方式(第三种常用):

1. 继承 Thread 类:

通过继承 Thread 类,并重写 run( ) 方法,直接创建线程对象。

class MyThread extends Thread {
    public void run() {
        System.out.println("hello MyThread");
    }
}
public class Demo1 {
    public static void main(String[] args) {
        Thread t = new MyThread();

        //启动线程
        t.start();
    }
}

在这里,有几个问题:

        1.此处的 run 方法有什么用呢?

        run方法是线程要执行的具体任务的载体。当一个线程启动后,其核心执行逻辑就写在 run 方法中。可以理解为是“线程的入口方法”。

        2. start 方法有什么用呢?

       start 方法的主要作用是启动一个新线程。当调用线程对象的 start 方法时,就会自动调用线程的 run 方法,开始执行 run 方法中的代码。

调用线程:

class MyThread extends Thread {
    public void run() {
        while(true) {
            System.out.println("hello MyThread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new MyThread();

        //启动线程
        t.start();

        //主线程执行的逻辑
        while(true) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

 我们正常调用 start 方法:

 可以看到结果是交替打印的,但是打印的先后顺序是不缺定的。

        Thread.sleep()是一个非常实用的线程控制方法,它属于 Thread 类的静态方法,用于使当前正在执行的线程暂停执行一段指定的时间(毫秒为单位)。使用这个方法需要异常处理。

        上面可以理解为,主线程到了 t.start() 方法后,新的线程与主线程(main)并行执行。各自执行对应的逻辑代码。

如果线程对象直接调用 run 方法,会怎么样呢?

class MyThread extends Thread {
    public void run() {
        while(true) {
            System.out.println("hello MyThread");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new MyThread();

        //调用 run 方法
        t.run();

        //主线程执行的逻辑
        while(true) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

        可以看到,结果只有 run 方法里的打印。

        当线程对象直接调用 run 方法时,不会启动新线程,而是在当前线程(即 main 线程)中执行 run 方法里的代码。需要把 run 方法执行完(run方法这里给了while死循环,所以执行不完run方法),才能执行到后面的 打印 hello main 循环。

2.实现 Runnable 接口:

实现 Runnable 接口,并重写 run 方法 ,并作为参数传递给 Thread 对象。

class MyRunnable implements Runnable {
    public void run() {
        System.out.println("hello Thread");
    }
}
public class Demo_A {
    public static void main(String[] args) {

        Runnable runnable = new MyRunnable();

        Thread t = new Thread(runnable);

        //启动线程
        t.start();

    }
}

3. 基于Lambda 表达式创建线程(推荐):

基本语法格式:

public class Demo_B {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
           //线程执行逻辑
           //......
        });

        //启动线程
        t.start();
    }
}

使用 Lambda 表达式创建线程时,不需要显式地重写 run 方法。

  () -> { //线程执行逻辑 } 这个 Lambda 表达式实现了Runnable接口的run方法。Lambda 表达式左侧的()表示run方法没有参数,右侧的代码块则是run方法的具体实现(相当于右侧的 { } 就是run方法)。

        因此,虽然代码里没有像传统方式那样显式地定义一个类去实现Runnable接口并重写run方法,但 Lambda 表达式已经隐式地完成了run方法的实现。

三、线程的几种属性:

 在此之前,先了解什么是前台线程,后台线程:

 前台线程:

        新创建的线程(比如main线程和自己新创建的线程)默认是前台线程。

        线程没运行完,进程就不会结束,(线程能够阻止进程结束)。

后台线程:

        线程没运行完,进程可以结束,(线程不能阻止进程结束)。

//设置 thread 线程为后台线程
thread.setDaemon(true);

属性 类型 获取方法 说明
线程ID long getId ( ) 唯一标识线程的ID,JVM分配,不同线程不重复。
线程名称 String getName() 默认格式为 Thread-N(如 Thread-0
线程优先级 int getPriority() 范围:1(最低)-10(最高),默认5。优先级仅作参考,操作系统可能不遵循。
线程状态 Thread.State getState() 状态包括:NEWRUNNABLEBLOCKEDWAITINGTIMED_WAITINGTERMINATED
是否后台线程 boolean isDaemon() 后台线程随主线程结束而终止,新创建的线程默认是前台线程。
是否存活 boolean isAlive() 线程是否已启动且未终止(start()后且未结束得到的是true)
是否被中断 boolean isInterrupted() 检查线程是否被中断,或主动中断线程。

代码示例:

public class Demo_B {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            System.out.println("子线程执行任务");
        }, "我的线程"); // 指定线程名称

        // 设置属性
        //设置线程优先级
        thread.setPriority(8);

        //设置线程为后台线程
        thread.setDaemon(true);

        // 启动线程
        thread.start();

        // 获取属性
        System.out.println("线程ID: " + thread.getId());
        System.out.println("线程名称: " + thread.getName());
        System.out.println("优先级: " + thread.getPriority());
        System.out.println("是否后台线程: " + thread.isDaemon());
        System.out.println("线程状态: " + thread.getState());
        System.out.println("是否存活: " + thread.isAlive());
    }
}

 此处的线程状态为 TERMINATED ;表示为代表线程已经执行完毕,处于终止状态。

四、线程的属性执行过程详解:

1、启动线程:

        线程通过 start 方法启动,并且一个线程对象只能调用一次 start 方法(如果调用两次会抛出 IllegalThreadStateException 异常)。

        一旦线程 启动后,代码会立即往下执行,不会产生任何阻塞等待(可以粗糙理解为代码停止了一会),比如:

//启动线程
t.start();
        
//主线程逻辑
//......

t.start()后,代码会立即往主线程逻辑继续执行,同时并行执行两个线程代码逻辑。

2、获取线程对象引用:

可借助 Thread.currentThread() 方法获取当前正在执行的线程对象引用。该方法是 Thread 类的静态方法,无论在何处调用,都能返回当前执行代码的线程对象。例如,在主线程中调用,就返回主线程的对象;在某个子线程中调用,就返回该子线程的对象。

public class Demo_C {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            // 在子线程中获取当前线程对象
            Thread threadName = Thread.currentThread();
        });

        //启动线程
        t.start();
    }
}

3、中断一个线程:

正常情况系下,一个线程,需要把入口方法执行完,才能使线程结束,有时候,又希望能够让这个线程提前结束(比如线程在 Thread.sleep()过程中)。

1、通过变量中断一个线程:

public class Demo_A {
    public static boolean flag = true;
    
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while(flag) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        //启动线程
        t.start();
        
        //让t线程执行 5 秒
        Thread.sleep(5000);

        //中断线程
        flag = false;
        
    }
}

我们定义了一个 flag 变量 ,作为 线程中while循环的“钥匙”,五秒过后,flag 置为 false ,线程也被终止。

2、直接使用线程内置的标志位 isinterruptted()

        Thread 对象中,包含了一个 boolean 变量,如果为 false,说明没有人去尝试终止这个线程;如果为true,说明有人尝试终止。

public class Demo_C {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello thread");
            }
        });

        //启动线程
        t.start();

        //主线程休眠 5 秒
        Thread.sleep(5000);

        //调用这个方法,就是把标志位 isInterrupted() 从false 改为 true
        t.interrupt();
    }
}

 线程while循环里 Thread.currentThread() 得到该线程的对象,再通过 t.interrupt() 控制该对象标志位得到返回值决定循环的开始和结束。

有一种特殊情况,就是当线程在sleep()的时候,会触发异常:

public class Demo_C {
    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) {
                    throw new RuntimeException(e);
                }
            }
        });

        //启动线程
        t.start();

        //主线程休眠 5 秒
        Thread.sleep(5000);

        //调用这个方法,就是把标志位 isInterrupted() 从false 改为 true
        t.interrupt();
    }
}

 

如果 t 线程在 sleep(),此时 main 中调用 interrupt()方法,就能把 sleep 唤醒。

sleep 提前唤醒,触发异常之后,然后 sleep 就会把标志位 isInterrupted 重置为 false。 

所以,也可以理解为这种情况相当于给我们更多可操作空间。

public class Demo_C {
    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) {
                    //throw new RuntimeException(e);
                    //添加break之后,此时触发异常的时候也会结束循环
                    //在break之前,也可以添加其他代码逻辑,相当于善后
                    for (int i = 3; i >= 0; i--) {
                        System.out.println("结束倒计时" + i);
                    }
                    
                    break;
                }
            }
        });

        //启动线程
        t.start();

        //主线程休眠 5 秒
        Thread.sleep(5000);

        //调用这个方法,就是把标志位 isInterrupted() 从false 改为 true
        t.interrupt();
    }
}

 所以,t 线程中进行了sleep()等阻塞操作,t 的 isInterrupted()还是会返回 true ,但是sleep()如果是被提前唤醒,抛出 InterruptException异常,同时也会把 isInterrupted()返回结果设置为 false,从而进入 catch 块执行相应的代码。此时需要手动决定是否要结束线程了。()

4、等待一个线程:

join()方法作用是阻塞当前线程,等待目标线程执行完毕。

常用于线程间顺序控制,例如主线程等待子线程完成后再处理结果。

public class Deom_D {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for (int i = 5; i > 0; i--) {
                System.out.println("子线程还有" + i + "秒做完任务");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        //启动线程
        t.start();

        //主线程(main)等待子线程(t)
        t.join();

        System.out.println("主线程结束");
    }
}

 由于join()也是会造成线程阻塞,所以main主线程  throws InterruptedException 。

要注意的是:

哪个线程中调用 join,该线程就是“等的一方”(此处是main)。

join 前面是哪个引用,对应的线程就是“被等的一方”(此处是 t )。

join 的核心方法:

方法签名 说明
void join() 无限期等待目标线程终止。
void join(long millis) 最多等待 millis 毫秒,超时后继续执行。(等的一方的线程继续执行)

五、线程的状态:

在上面的线程的几种属性的表格中,就有列举出,现在讲解其中部分线程状态

状态 枚举值 说明
新建(NEW) NEW 线程对象已创建,但未调用 start() 方法。
终止(TERMINATED) TERMINATED 线程已执行完毕(run() 方法结束)或异常终止。
可运行(RUNNABLE) RUNNABLE 线程已启动(start() 被调用),正在 JVM 中等待 CPU 时间片或正在执行
等待(WAITING) WAITING 线程无限期等待其他线程的唤醒信号(如  join())。
超时等待(TIMED_WAITING) TIMED_WAITING 线程在指定时间内等待唤醒(如 sleep(n)join(n))。

怎么查看呢?

比如有这样一段代码:

public class Deom_D {
    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();

        //主线程(main)等待子线程(t)
        t.join();

        System.out.println("主线程结束");
    }
}

这里 子线程的名字为 “我的线程” ,主线程名字默认为 “main”

        这两个线程对应的状态应该为  超时等待(TIMED_WAITING)  和 等待(WAITING)

怎么查看?

在安装jdk文件夹目录下找到这个:

 连接对应的线程后可以看到: