Skip to content

多线程

基本概念

程序概念

一些列代码指令的集合统称 ,应用,软件等等 都属于程序。

程序运行必须依托于进程而存在,进程负责分配资源,依赖线程来运行。

单核心配置

ts
运行 - msconfig - 引导 - 高级选项 - 处理器个数为 1

进程定义

进行中应用程序中属于资源分配的基本单位 。

应用程序执行的实例,拥有独立的内存空间和CPU资源。

线程定义

线程是包含在进程之中的, 一个进程至少有一个线程,否则将无法运行,线程是CPU调度运算的基本单位。

线程是CPU调度和分派的基本单位,应用程序运算是最小单位。

多线程

  • 一个进程中同时运行了多个线程,用来完成不同的工作,则称之为"多线程"。

  • 单核CPU下,多个线程交替占用CPU资源,而非真正的并行执行。

线程开辟和线程执行

线程不是越多越好,要结合实际的硬件环境来决定。

ts
在单核心CPU下,多个线程是轮流交替执行的,以windows操作系统为例,多个线程随机轮流交替执行,每个线程最多执行20ms,然后继续切换下一个线程,而非并行执行,因为切换的频率非常快,所以我们感知不到这个过程,宏观上是同时执行的,实际上,是轮流交替执行的。

并发并行

并发:同时发生,轮流交替执行;宏观上同时执行,微观轮流交替执行。

并行:严格意义上的同时执行,相当于一个请求生成一个线程。

主线程

main方法为程序的入口,底层由main线程负责执行,由JVM自动调用执行。

Thread类

  • java.lang.Thread 线程类

  • 优先级越高执行的概率越高。

属性

java
最高的优先级是10
最低的优先级是1
默认的优先级是5

构造方法

java
// 无参
Thread() 

// 传入一个Runnable接口的实现类
Thread(Runnable target)

// 传入一个Runnable接口的实现类,再传入线程的名称
Thread(Runnable target, String name) 

// 传入一个String类型的作为线程的名字
Thread(String name)

方法

getName()

java
作用:获取线程名称
    
参数:无
    
返回值:String类,
    
示例:
// 获取当前Thread类对象(线程对象)
Thread thread = Thread.currentThread();
// 打印线程名称
System.out.println("当前线程对象 " + thread); // Thread[main,5,main]
// 获取当前线程对象名称
String name = thread.getName();
System.out.println("当前线程名称 " + name); // main

currentThread()

java
作用:(静态方法)获取当前线程对象
    
参数:无
    
返回值:Thread类型,线程对象
    
示例:
// 获取当前Thread类对象(线程对象)
Thread thread = Thread.currentThread();
// 打印线程名称
System.out.println("当前线程对象 " + thread); // Thread[main,5,main]

setName(String name)

java
作用:设置线程名称
    
参数:String类型
    
返回值:无
    
示例:
// 获取当前Thread类对象(线程对象)
Thread thread = Thread.currentThread();
// 打印线程名称
System.out.println("当前线程对象 " + thread); // Thread[main,5,main]
// 获取当前线程对象名称
String name = thread.getName();
System.out.println("当前线程名称 " + name); // main
// 设置当前线程名称
thread.setName("主线程");

start()

java
作用:开启线程,向CPU提示自己准备就绪,可以被执行。
    
参数:无
    
返回值:无
    
示例:
// 创建 自定义线程对象
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();

// 设置线程名称
myThread1.setName("线程A");
myThread2.setName("线程B");

// 启动线程
myThread1.start();
myThread2.start();

sleep()

java
作用:休眠进程,进入阻塞状态。
    
参数:long 类型的 毫秒数
    
返回值:无
    
示例:
public class MyThread extends Thread{
    @Override
    public void run() { // 运行
        // 自定义线程执行的代码
        // 遍历当前线程的名称
        for (int i = 0;i<=20;i++){
            try {
                // 方法重写,因为父类run方法没有声明任何异常 所以子类也不能声明任何异常
                // 不能通过 throws xxx 的形式在方法上声明异常,只能捕获处理异常。
                // 线程睡眠,进入阻塞状态
                Thread.sleep(3000); // 休眠3秒钟 到达时间  自动继续执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "执行, 第" + i + "次");
        }
        // 线程执行完毕
        System.out.println("线程执行完毕");
        // 线程挂了
    }
}

setPriority()

java
作用:设置线程的优先级,默认为5,范围1-10。从1~10,1最低,10最高,默认为5,优先级高的线程只是获得CPU资源的概率较大,并不一定能够保证优先执行;
    
参数:int类型
    
返回值:无
    
示例:
// 创建 自定义线程对象
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();

// 设置线程的优先级 方式1
myThread1.setPriority(10);
// 设置线程优先级 方式2
// myThread1.setPriority(MAX_PRIORITY); //MAX_PRIORITY-10  NORM_PRIORITY-5  MIN_PRIORITY-1
// 获取线程的优先级
int priority1 = myThread1.getPriority();// 线程A 的优先级为 10
int priority2 = myThread2.getPriority(); // 线程B 的优先级为 5
java
package com.ThreadPart;

public class MyThread extends Thread{
    @Override
    public void run() { // 运行
        // 自定义线程执行的代码
        // 遍历当前线程的名称
        for (int i = 0;i<=20;i++){
            System.out.println(Thread.currentThread().getName() + "执行, 第" + i + "次");
        }
        // 线程执行完毕
        System.out.println("线程执行完毕");
        // 线程挂了
    }
}

getPriority()

java
作用:打印线程的优先级,默认为5,范围1-10
    
参数:无
    
返回值:Int类型
    
示例:
// 创建 自定义线程对象
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();

// 设置线程的优先级
myThread1.setPriority(10);
// 获取线程的优先级
int priority1 = myThread1.getPriority();// 线程A 的优先级为 10
int priority2 = myThread2.getPriority(); // 线程B 的优先级为 5

join()

java
作用:线程插队,直到插队线程执行完毕。
    
参数:无
    
返回值:无
    
示例:
java
public static void main(String[] args) throws InterruptedException {
    // 创建 自定义线程对象
    MyThread myThread1 = new MyThread();
    MyThread myThread2 = new MyThread();

    // 设置线程名称
    myThread1.setName("线程A");
    myThread2.setName("线程B");

    // 启动线程
    myThread1.start();
    myThread2.start();

    // 主线程遍历打印
    for (int i = 0;i<=20;i++){
        // 当主线程执行到第11次遍历时,线程A加队,优先执行完毕,再执行主线程
        if (i == 10){
            // 线程A优先执行完毕后,主线程再执行
            myThread1.join();
        }
        System.out.println("主线程main执行" + "第" +i + "次");
    }
}

join(long millis)

java
作用:线程插队,指定插队时间,只允许在插队的时间优先执行,过了时间按照随机轮流执行。
    
参数:long 类型 500毫秒
    
返回值:无
    
示例:
java
public static void main(String[] args) throws InterruptedException {
    // 创建 自定义线程对象
    MyThread myThread1 = new MyThread();
    MyThread myThread2 = new MyThread();

    // 设置线程名称
    myThread1.setName("线程A");
    myThread2.setName("线程B");

    // 启动线程
    myThread1.start();
    myThread2.start();

    // 主线程遍历打印
    for (int i = 0;i<=20;i++){
        // 当主线程执行到第11次遍历时,线程A加队,优先执行1000毫秒时间,然后主线程继续执行
        if (i == 10){
            // 线程A优先执行1000毫秒时间,然后主线程继续执行
            myThread1.join(1000);
        }
        System.out.println("主线程main执行" + "第" +i + "次");
    }
}

join(long millis,int nanos)

java
作用:线程插队,指定插队时间,只允许在插队的时间优先执行,过了时间按照随机轮流执行。
    
参数:long 类型 500毫秒,第二个参数是纳秒
    
返回值:无
    
示例:
java
public static void main(String[] args) throws InterruptedException {
    // 创建 自定义线程对象
    MyThread myThread1 = new MyThread();
    MyThread myThread2 = new MyThread();

    // 设置线程名称
    myThread1.setName("线程A");
    myThread2.setName("线程B");

    // 启动线程
    myThread1.start();
    myThread2.start();

    // 主线程遍历打印
    for (int i = 0;i<=20;i++){
        // 当主线程执行到第11次遍历时,线程A加队,优先执行1000毫秒时间,然后主线程继续执行
        if (i == 10){
            // 线程A优先执行1000毫秒,2000纳秒时间,然后主线程继续执行
            myThread1.join(1000,2000);
        }
        System.out.println("主线程main执行" + "第" +i + "次");
    }
}

yield()

java
作用:线程礼让,当前线程向调度器发出信息,表示当前线程正在执行的线程愿意让步,但是调度器可以忽略这个信息。
    
参数:无
    
返回值:
    
注意:线程的礼让,可能会礼让不成功,但是插队是一定能够插队成功的。
    
示例:
java
package com.ThreadPart;

public class TestMyThread {
    public static void main(String[] args) throws InterruptedException {
        // 创建 自定义线程对象
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();

        // 设置线程名称
        myThread1.setName("线程A");
        myThread2.setName("线程B");

        // 设置线程的优先级
        // myThread1.setPriority(10);

        // 获取线程的优先级
        int priority1 = myThread1.getPriority();// 线程A 的优先级为 10
        int priority2 = myThread2.getPriority(); // 线程B 的优先级为 5
        // 打印优先级
        System.out.println(myThread1.getName() + " 的优先级为 " + priority1); // 线程A优先运行完毕
        System.out.println(myThread2.getName() + " 的优先级为 " + priority2);

        // 启动线程
        myThread1.start();
        myThread2.start();

        // 主线程遍历打印
        // for (int i = 0;i<=20;i++){
        //     // 当主线程执行到第11次遍历时,线程A加队,优先执行完毕,再执行主线程
        //     if (i == 10){
        //         myThread1.join(1000,2000);
        //     }
        //     System.out.println("主线程main执行" + "第" +i + "次");
        // }
    }
}
java
package com.ThreadPart;

public class MyThread extends Thread{
    @Override
    public void run() { // 运行
        // 自定义线程执行的代码
        // 遍历当前线程的名称
        for (int i = 0;i<=20;i++){
            try {
                // 线程睡眠,阻塞状态
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 当i取值为10  则礼让 但不保证一定会礼让成功
            if (i == 10){
                System.out.println(Thread.currentThread().getName() + "线程礼让了");
                Thread.yield(); // 线程礼让
            }
            System.out.println(Thread.currentThread().getName() + "执行, 第" + i + "次");
        }
        // 线程执行完毕
        System.out.println(Thread.currentThread().getName() + "线程执行完毕");
        // 线程挂了
    }
}

创建线程

创建的子线程如果没有指定名称,将默认以 Thread-0 -1,这种方式来命名;

java
实现 Runnable 接口,并不是为了获得一个对象,而是为了定义线程要执行的任务。
    在 Java 中,如果你创建一个类去实现 Runnable 接口,目的就是把这个类的 run() 方法作为线程要执行的逻辑。它没有返回值,也不能抛出受检异常,它的作用仅仅是:把你要让线程做的事情写在 run() 方法里。
    
    
继承 Thread 的类,其作用是:
	把你要执行的任务封装在 run() 方法中,并通过线程对象直接启动。是直接把你的“任务”写在了线程类的 run() 方法中,然后通过 start() 启动线程。这个类的作用就是:定义线程要执行的任务(写在 run() 里),并直接成为一个线程对象,可以被 .start() 启动。你定义的类是一个线程本身,它继承了 Thread,可以直接拿来运行。

方式1:继承Thread类,重写run方法

java
package com.ThreadPart;

public class MyThread extends Thread{
    @Override
    public void run() {
        // 自定义线程执行的代码
        // 遍历当前线程的名称
        for (int i = 0;i<=20;i++){
            System.out.println(Thread.currentThread().getName() + "执行, 第" + i + "次");
        }
    }
}

创建线程对象,启用线程。

java
package com.ThreadPart;

public class TestMyThread {
    public static void main(String[] args) {
        // 创建 自定义线程对象
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();

        // 设置线程名称
        myThread1.setName("线程A");
        myThread2.setName("线程B");

        // 启动线程
        myThread1.start();
        myThread2.start();
    }
}

方式2:实现Runnable接口,重写run方法

  • 实现Runnable接口 重写run方法 Runnable实现类可以作为参数构造Thread实例
java
package com.ThreadPart;

public class RunnableImpl implements Runnable{

    @Override
    public void run() {
        for (int i = 0;i<=20;i++){
            System.out.println(Thread.currentThread().getName() + "执行, 第" + i + "次");
        }
    }
}

创建线程对象,使用Runnable实现类作为参数的构造方法创建实例

java
package com.ThreadPart;

public class TestMyThread2 {
    public static void main(String[] args) {
        // 创建Runnable实现类对象
        RunnableImpl runnable1 = new RunnableImpl();

        // 使用Thread的构造函数创建线程对象
        Thread thread1 = new Thread(runnable1,"线程A");
        Thread thread2 = new Thread(runnable1,"线程B");

        // 准备就绪 
        thread1.start();
        thread2.start();
    }
}
  • 两种创建方式的区别(重点掌握)
java
1、继承Thread类,编写简单,可直接操作线程,适用于单继承;
2、实现Runnable接口,避免单继承局限性,便于共享资源;
  • 调用start方法和run方法的区别(重点掌握)
java
1、调用start方法表示通知调度器(CPU)当前线程准备就绪,调度器会开启新的线程来执行任务;
2、调用run方法表示使用当前主线程来执行方法,不会开启新的线程;

两种方式的对比

image-20250806115416880

线程状态

image-20250801103243194

线程的5种状态:创建、就绪、运行、阻塞、死亡;

线程优先级

  • 优先级高的线程获取CPU资源的概率较大,并不保证优先执行,哪个线程执行是由调度器CPU来决定。

优先级示例

java
package com.ThreadPart;

public class MyThread extends Thread{
    @Override
    public void run() { // 运行
        // 自定义线程执行的代码
        // 遍历当前线程的名称
        for (int i = 0;i<=20;i++){
            try {
                // 线程睡眠,阻塞状态
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 当i取值为10  则礼让 但不保证一定会礼让成功
            // if (i == 10){
            //     System.out.println(Thread.currentThread().getName() + "线程礼让了");
            //     Thread.yield(); // 线程礼让
            // }
            System.out.println(Thread.currentThread().getName() + "执行, 第" + i + "次");
        }
        // 线程执行完毕
        System.out.println(Thread.currentThread().getName() + "线程执行完毕");
        // 线程挂了
    }
}
java
package com.ThreadPart;

public class TestMyThread {
    public static void main(String[] args) throws InterruptedException {
        // 创建 自定义线程对象
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();

        // 设置线程名称
        myThread1.setName("线程A");
        myThread2.setName("线程B");

        // 设置线程的优先级
        myThread1.setPriority(10);

        // 获取线程的优先级
        int priority1 = myThread1.getPriority();// 线程A 的优先级为 10
        int priority2 = myThread2.getPriority(); // 线程B 的优先级为 5
        // 打印优先级
        System.out.println(myThread1.getName() + " 的优先级为 " + priority1); // 线程A优先运行完毕
        System.out.println(myThread2.getName() + " 的优先级为 " + priority2);

        // 启动线程
        myThread1.start();
        myThread2.start();

    }
}

线程案例

爬山案例:模拟多人爬山。

java
需求:
    每个线程代表一个人;
    可设置每人爬山速度;
    每爬完100米显示信息;
    爬到终点时给出相应提示;
java
package com.ThreadPart;

/**
 *  模拟多人爬山
 *  分析:将run方法体的内容,作为两个角色共同执行的爬山过程,爬山的速度不一样,所以表示休眠的时间不同;
 *  爬山的高度是相同的,角色名称不同,表示线程名不同,同时创建两个线程对象,分别 start方法,表示开始;
 *  同时爬山,因为爬山速度不同,所以到达山顶时间是不同的.
 */

public class ClimbMountain extends Thread{
    private String name;
    private int length; // 长度 高度
    private int time; // 每爬100米耗时
    // 全参构造方法
    public ClimbMountain(String name, int length, int time) {
        // 通过父类的Thread构造方法设置线程名称
        super(name);
        this.name = name;
        this.length = length;
    }
    // 重写run方法
    @Override
    public void run() {
        // 循环遍历
        while (length > 0){
            // 不同通过 throws 的方式声明异常,因为是方法重写,不能超出父类的声明异常的范围
            // 所以只能通过trycatch的方式捕获异常
            try {
                // 进程休眠,模拟爬山时间
                Thread.sleep(time);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 每次爬山 高度降低100米
            length -= 100;
            // 提示信息
            System.out.println(Thread.currentThread().getName() + "爬了100米,剩余" + length +"米");
        }
        // 循环结束
        System.out.println(Thread.currentThread().getName() + "到达了山顶");
    }
    // 测试方法-同时也是主线程
    public static void main(String[] args) {
        // 创建爬山线程
        ClimbMountain caixukun = new ClimbMountain("蔡徐坤",3000,200);
        ClimbMountain mabaoguo = new ClimbMountain("马宝国",3000,400);

        // 开启线程
        caixukun.start();
        mabaoguo.start();
    }
}

模拟叫号看病案例

java
需求:
某科室一天需看普通号50个,特需号10个 (执行不同的次数)
特需号看病时间是普通号的2倍 (休眠时间)
开始时普通号和特需号并行叫号,叫到特需号的概率比普通号高(同时start开始执行 优先级不同)
当普通号叫完第10号时,要求先看完全部特需号,再看普通号 (插队)
使用多线程模拟这一过程

分析:子线程作为特需号类  主线程作为普通号类
java
package com.ThreadPart;

public class Special extends Thread{
    @Override
    public void run() {
        // 循环10次,作为10次号
        for (int i = 0;i < 10;i++){
            // 看病时间
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 提示信息
            System.out.println(Thread.currentThread().getName() + "第" + i + "号在看病");
        }
        System.out.println(Thread.currentThread().getName() + "看病完毕");
    }
    // main线程作为普通号
    public static void main(String[] args) throws InterruptedException {
        // 创建对象
        Special special = new Special();
        // 设置线程名称
        special.setName("*****特需号*****");
        // 设置线程优先级
        special.setPriority(MAX_PRIORITY);
        // 开启线程
        special.start();
        // 获取当前线程对象
        Thread mainThread = Thread.currentThread();
        // 设置当前线程对象的线程名称
        mainThread.setName("普通号");
        // 遍历普通号过程
        for(int i = 1;i <= 50;i++){
            Thread.sleep(500);
            System.out.println(Thread.currentThread().getName() + "第" + i + "号在看病");

            if(i == 10){
                // 当遍历到第10号看病时,特殊号加队
                special.join();
            }
        }
        System.out.println(Thread.currentThread().getName() + "看病完毕");
    }
}

同步关键字

synchronized(同步)关键字

可以用于修饰方法和代码块,分别表示同一时间只能有一个线程访问这个方法或者这个代码块。(需要排队,效率低)

基本使用

java
// 修饰方法(同步方法)
// 可以用于修饰方法 ,表示同一时间只能有一个线程访问这个方法
public synchronized void fangfaming(){
    
}


// 修饰代码块(同步代码块) / 同步代码块锁定的范围更加精确。
// 同步锁的是当前这个对象的访问权限,只有多个线程中访问的是同一个(Runnable)对象,才拥有锁定的效果。
// 当一个线程访问一个synchronized(this)同步代码块时,其他synchronized(this)同步代码块同样被锁定。
// 当一个线程访问一个synchronized(this)同步代码块时,其他线程可以访问该资源的非synchronized(this)同步代码
synchronized(this){ // this 表示当前实现类的对象
    
}

使用背景

ts
当 ticketCount 为 2时,三个线程都进入了循环判断,然后进入休眠状态,休眠过后就会出现,三个线程继续往后执行代码,这个时候就出现了,不符合逻辑的情况,线程之间没有先后顺序,没有互相制约的效果,会导致票重复卖出,或者超卖的情况。

解决方案:多个线程必须排队买票,保证同一时间只能有一个线程(访问这段代码)买票,上一个线程执行完毕之后,下一个线程才能继续购买。

案例1:默认使用多线程出现的问题:

java
package com.ThreadPart;

// 未使用 sync线程同步关键字
public class BuyTicket1 implements Runnable{
    // 定义属性
    int ticketCount = 10;

    @Override
    public void run() {
        while (ticketCount > 0){
            // 线程休眠-保证每个线程都有机会抢到票
            try {
                Thread.sleep(500); // 三个线程都进入睡眠
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // ticketCount--; 线程1执行完之后,线程2在循环体内,仍会执行
            ticketCount--;
            // 提示信息
            System.out.println(Thread.currentThread().getName() + "抢到了第" + (10 - ticketCount) + "张票,还剩余" + ticketCount + "张票");
        }
        System.out.println("票卖完了");
    }

    // 测试类
    public static void main(String[] args) {
        // 创建Runnable对象
        BuyTicket1 runnable = new BuyTicket1();
        // 创建线程对象
        Thread th1 = new Thread(runnable, "赵四");
        Thread th2 = new Thread(runnable, "广坤");
        Thread th3 = new Thread(runnable, "大拿");

        th1.start();
        th2.start();
        th3.start();
    }
}

案例2:使用方式2来创建线程,同时设置同步关键字,修饰代码块,保证线程安全。

  • 同步代码块锁定的范围更加精确
java
package com.ThreadPart;

public class BuyTicket2 implements Runnable{
    // 定义属性
    int ticketCount = 10;

    @Override
    public void run() {
        while (true){
            // 首先进入方法中,三个线程都先进行休眠,当有一个线程苏醒了,然后进入代码执行,他执行完之后,后面苏醒的线程才有机会进入访问。
            // 线程休眠-保证每个线程都有机会抢到票
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 使用 synchronized 修饰代码块
            // 三个线程对象 共享一个 Runnable实现类对象,保证唯一的一份,ticketCount为10;
            synchronized (this){
                if(ticketCount == 0){
                    break;
                }
                // 卖票
                ticketCount--;
                // 提示信息
                System.out.println(Thread.currentThread().getName() + "抢到了第" + (10 - ticketCount) + "张票,还剩余" + ticketCount + "张票");
            }
        }
        System.out.println("票卖完了");

    }
    // 测试类
    public static void main(String[] args) {
        // 创建Runnable对象
        // 三个线程对象 共享一个 Runnable实现类对象,保证唯一的一份,ticketCount为10;
        BuyTicket2 runnable = new BuyTicket2();
        // 创建线程对象
        Thread th1 = new Thread(runnable, "赵四");
        Thread th2 = new Thread(runnable, "广坤");
        Thread th3 = new Thread(runnable, "大拿");

        th1.start();
        th2.start();
        th3.start();
    }
}

案例3:使用方式2来创建线程,同时设置同步关键字,修饰方法,保证线程安全。

  • 可以用于修饰方法 ,表示同一时间只能有一个线程访问这个方法
java
package com.ThreadPart;

public class BuyTicket2 implements Runnable{
    // 定义属性
    int ticketCount = 10;

    @Override
    public synchronized void run() {
        while (ticketCount > 0){
            // 线程休眠-保证每个线程都有机会抢到票
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 卖票
            ticketCount--;
            // 提示信息
            System.out.println(Thread.currentThread().getName() + "抢到了第" + (10 - ticketCount) + "张票,还剩余" + ticketCount + "张票");
        }
        System.out.println("票卖完了");

    }
    // 测试类
    public static void main(String[] args) {
        // 创建Runnable对象
        BuyTicket2 runnable = new BuyTicket2();
        // 创建线程对象
        Thread th1 = new Thread(runnable, "赵四");
        Thread th2 = new Thread(runnable, "广坤");
        Thread th3 = new Thread(runnable, "大拿");

        th1.start();
        th2.start();
        th3.start();
    }
}

案例4:使用方式2来创建线程,同时设置同步关键字,修饰代码块,同步代码块中的this。

java
// 同步锁的是当前这个对象的访问权限,this 指向多个线程中访问的是同一个(Runnable)对象,才拥有锁定的效果。只要是 多个线程访问的是同一个对象空间就可以

package com.ThreadPart;

public class BuyTicket2 implements Runnable{
    // 定义属性
    int ticketCount = 10;
    // 创建一个对象
    Object obj = new Object();
    // 重写run方法
    @Override
    public void run() {
        while (true){
            // 线程休眠-保证每个线程都有机会抢到票
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 这里使用 Runnable实现类对象中的
            synchronized (obj){
                if (ticketCount == 0){
                    break;
                }
                // 卖票
                ticketCount--;
                // 提示信息
                System.out.println(Thread.currentThread().getName() + "抢到了第" + (10 - ticketCount) + "张票,还剩余" + ticketCount + "张票");
            }
        }
        System.out.println("票卖完了");
    }
    // 测试类
    public static void main(String[] args) {
        // 创建Runnable对象
        BuyTicket2 runnable = new BuyTicket2();
        // 创建线程对象
        Thread th1 = new Thread(runnable, "赵四");
        Thread th2 = new Thread(runnable, "广坤");
        Thread th3 = new Thread(runnable, "大拿");

        th1.start();
        th2.start();
        th3.start();
    }
}

线程安全

之前接触到线程安全的类 StringBuffer Vector Hashtable 都是使用同步关键字synchronized修饰方法实现的线程安全。

生产者消费者

不属于设计模式,属于线程之间通信的一种现象。

  • 生产什么,消费什么
  • 没有生产,不能消费(持续生产,持续消费)
  • 必须保证产品的完整性
  • 不能重复消费

多线程就相当于多个人,每个人做单独的工作,而不是靠主线程,一个人干所有的活。

协调每个人工作的顺序,通过wait方法和notice方法,协调每个人干活的顺序。

Object类方法

wait()

java
wait():导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
    
参数:无
    
返回值:无

例子:
public class Producer extends Thread{
    // 属性是 Computer类对象
    private Computer computer;

    public Producer(Computer computer) {
        this.computer = computer;
    }
    synchronized (computer){
        // 如果flag为false则代表可以生产,true不能生产
        if (computer.isFlag()){
            // 线程等待
            try {
                computer.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        ...
        // 唤醒线程,随机唤醒 等待的线程
		computer.notify();
    }
}

notify()

java
notify():唤醒正在等待对象监视器的单个线程
    
参数:无
    
返回值:无

例子:
public class Producer extends Thread{
    // 属性是 Computer类对象
    private Computer computer;

    public Producer(Computer computer) {
        this.computer = computer;
    }
    synchronized (computer){
        // 如果flag为false则代表可以生产,true不能生产
        if (computer.isFlag()){
            // 线程等待
            try {
                computer.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        ...
        // 唤醒线程,随机唤醒 等待的线程
		computer.notify();
    }
}

生产者消费者案例:

电脑类

java
package com.moreThread;

public class Computer {
    // 主机和显示器属性
    private String host;
    private String monitor;
    // 标记,false 可以生产,不能消费;true 可以消费,不能生产;
    private boolean flag;

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public String getMonitor() {
        return monitor;
    }

    public void setMonitor(String monitor) {
        this.monitor = monitor;
    }

    public boolean isFlag() {
        return flag;
    }

    public void setFlag(boolean flag) {
        this.flag = flag;
    }

    public Computer(String host, String monitor, boolean flag) {
        this.host = host;
        this.monitor = monitor;
        this.flag = flag;
    }

    public Computer() {
    }

    @Override
    public String toString() {
        return "Computer{" +
                "host='" + host + '\'' +
                ", monitor='" + monitor + '\'' +
                ", flag=" + flag +
                '}';
    }
}

生产者类,生产者线程用于生产产品

java
package com.moreThread;

// 生产线程,用于生产电脑
public class Producer extends Thread{
    // 属性是 Computer类对象
    private Computer computer;

    public Producer(Computer computer) {
        this.computer = computer;
    }

    @Override
    public void run() {
        for (int i = 1;i<=20;i++){
            // 设置同步关键字
            synchronized (computer){
                // 如果flag为false则代表可以生产,true不能生产
                if (computer.isFlag()){
                    // 停止生产电脑
                    try {
                        computer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 如果 i 为偶数,生产联想电脑,否则生产华硕电脑
                if (i % 2 == 0){
                    computer.setHost(i + "号" + "联想电脑主机");
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    computer.setMonitor(i + "号" + "联想电脑显示器");
                    System.out.println("厂家 生产了第"+ i +"号联想电脑");
                }else{
                    computer.setHost(i + "号" + "华硕电脑主机");
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    computer.setMonitor(i + "号" + "华硕电脑显示器");
                    System.out.println("厂家 生产了第"+ i +"号华硕电脑");
                }
                // 设置生产标记为 true ,代表可以消费
                computer.setFlag(true);
                // 唤醒消费线程,随机唤醒 等待的线程
                computer.notify();
            }
        }
    }
}

消费者类

java
package com.moreThread;

// 消费者线程 负责消费电脑
public class Consumer extends Thread{
    // 设置电脑属性
    private Computer computer;

    public Consumer(Computer computer) {
        this.computer = computer;
    }

    @Override
    public void run() {
        for (int i =1 ; i <= 20 ;i++){
            synchronized (computer){
                // 如果标记为false则代表,不能消费,则把线程等待
                if (computer.isFlag() == false ){
                    try {
                        computer.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 可以消费
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("消费者 购买了" + computer.getHost() + "和" + computer.getMonitor());
                // 设置标记为false,代表消费过了,需要生产才能继续消费
                computer.setFlag(false);
                // 唤醒线程,随机唤醒 等待的线程
                computer.notify();
            }
        }
    }
}

测试类

java
package com.moreThread;

public class TestComputer {
    public static void main(String[] args) {
        // 创建对象
        Computer computer = new Computer();

        // 创建生产者线程
        Producer producer = new Producer(computer);
        Consumer consumer = new Consumer(computer);

        // 开启线程
        producer.start();
        consumer.start();
    }
}

ArrayBlockingQueue类

使用 ArrayBlockingQueue类中的方法,优化生产者消费者模式。

ArrayBlockingQueue 用于基于数组的阻塞队列;队列,即FIFO First In First Out;阻塞表示队列是有长度限制的,所以当队列满了以后,将不能再添加新的数据到队列中;

构造方法

java
// 创建 阻塞队列对象,,参数为 队列的长度
ArrayBlockingQueue<Computer> queue = new ArrayBlockingQueue<>(20);

方法

add()

java
作用:在插入此队列的尾部,如果有可能立即这样做不超过该队列的容量,返回指定的元素。
    
参数:无
    
返回值:无
    
示例:
    
// 定义属性 数组阻塞队列 对象
private ArrayBlockingQueue<Computer> queue;

public Producer(ArrayBlockingQueue<Computer> queue) {
    this.queue = queue;
}    
 // 把生产的对象添加到队列
queue.add(computer);

take()

java
作用:从队列中取出数据。
    
参数:无
    
返回值:返回指定的元素
    
示例:
// 定义属性 数组阻塞队列 对象
private ArrayBlockingQueue<Computer> queue;

public Producer(ArrayBlockingQueue<Computer> queue) {
    this.queue = queue;
}    
 // 取出对象
Object obj = queue.take();

案例:队列优化生产者消费者模式

电脑类

java
package com.moreThread;

public class Computer {
    // 主机和显示器属性
    private String host;
    private String monitor;
    // 不用再设置 标记

    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public String getMonitor() {
        return monitor;
    }

    public void setMonitor(String monitor) {
        this.monitor = monitor;
    }

    public Computer(String host, String monitor) {
        this.host = host;
        this.monitor = monitor;
    }

    public Computer() {

    }

    @Override
    public String toString() {
        return "Computer{" +
                "host='" + host + '\'' +
                ", monitor='" + monitor + '\'' +
                '}';
    }
}

生产者类

java
package com.moreThread;

import java.util.concurrent.ArrayBlockingQueue;

// 生产线程,用于生产电脑
public class Producer extends Thread{

    // 定义属性 数组阻塞队列 对象
    private ArrayBlockingQueue<Computer> queue;

    public Producer(ArrayBlockingQueue<Computer> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        for (int i = 1;i<=20;i++){
            // 创建电脑产品对象
            Computer computer = new Computer();
            // 生产电脑
            // 如果 i 为偶数,生产联想电脑,否则生产华硕电脑
            if (i % 2 == 0){
                computer.setHost(i + "号" + "联想电脑主机");
                computer.setMonitor(i + "号" + "联想电脑显示器");
                System.out.println("厂家 生产了第"+ i +"号联想电脑");
                // 把生产的对象添加到队列
                queue.add(computer);
            }else{
                computer.setHost(i + "号" + "华硕电脑主机");
                computer.setMonitor(i + "号" + "华硕电脑显示器");
                System.out.println("厂家 生产了第"+ i +"号华硕电脑");
                // 把生产的对象添加到队列
                queue.add(computer);
            }
        }
    }
}

消费者类

java
package com.moreThread;

import java.util.concurrent.ArrayBlockingQueue;

// 消费者线程 负责消费电脑
public class Consumer extends Thread{
    // 设置属性为 队列
    private ArrayBlockingQueue queue;

    public Consumer(ArrayBlockingQueue queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        for (int i =1 ; i <= 20 ;i++){
            Computer computer = new Computer();
            // 消费电脑
            try {
                // 取出电脑属性
                Object obj = queue.take();
                System.out.println("消费者消费了" + (Computer)obj);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

测试类

java
package com.moreThread;

import java.util.concurrent.ArrayBlockingQueue;

public class TestComputer {
    public static void main(String[] args) {
        // 创建 阻塞队列对象,并设置队列的长度为20
        ArrayBlockingQueue<Computer> queue = new ArrayBlockingQueue<>(20);

        // 创建线程对象,参数是有参构造,同时参数是同一个队列
        Producer producer = new Producer(queue);
        Consumer consumer = new Consumer(queue);

        // 开始线程
        producer.start();
        consumer.start();

    }
}

相关面试题

sleep()方法和wait()方法的区别?

1、sleep属于Thread类中的静态方法,wait()属于Object类中的实例方法。

2、sleep不会释放锁,wait会释放锁,synchronized,sleep休眠的线程会阻止其他线程进入代码块。

notify() 方法 和 notifyAll() 方法的区别?

1、notify() 是随机唤醒一个等待的线程,即执行了wait方法的线程。

2、notifyAll() 是唤醒所有等待的线程。

Lock接口

是一个接口,有很多实现类,有读的锁和写的锁,读和读之间不互斥,读写互斥,写写互斥。用于解决线程不安全问题。

锁的分类

锁的分类方式1(两大类):

  • 乐观锁:多线程场景下,A线程操作这段代码,默认认为其他线程不会对这段代码进行操作。例如:自旋锁,无锁,CAS(Compare and swap)。

  • 悲观锁:默认认为所有的线程都会操作这段代码,于是把这段代码加上锁,只允许一个线程操作。例如 同步关键字,会一直等待,不会换其他空的操作,效率会很低,很死板。

描述CAS的实现原理:

java
例如 a = a + 1 ; 这个操作;
    
属于乐观锁的思想体现,默认所有线程不会改变当前线程操作的数据,所以根本就不上锁,每次会把数据a读取到,进行一些操作之后,再去覆盖原来的数据,如果原来的数据没有改变,则认为它是线程安全的,没有其他线程操作过,直接覆盖就可以;
    如果原来的数据发生了改变,就会认为有线程操作了数据,然后就再次把数据读取到,再进行一次操作,再去赋值,一直重复这个过程,一直自旋,当原来的数据没有发生改变时,进行覆盖。

锁的分类方式2(两大类):

公平锁:先来先得,排队公平。

非公平锁:允许“插队”,可能获得更高吞吐量。

ReentrantLock类

ReentrantLock类是Lock接口的实现类。叫作可重入锁。ReentrantLock 是一种更灵活、功能更丰富的 独占锁机制,适用于对锁控制要求较高的并发编程场景。

  • 同一个线程,多次获得一个锁对象,不应该产生死锁。
  • 同一时刻只允许一个线程持有该锁,其他线程必须等待。
  • 如果一个线程已经获得了锁,它可以再次获得锁而不会被阻塞(重入次数会记录)。
  • 在获取锁的过程中可以响应中断。
  • 可以通过构造函数选择是否启用公平锁。
ts
* 死锁:死锁是因为逻辑错误导致的多个线程同时竞争同一个资源,僵持不下,导致的线程互相等待的现象。
* 死锁:两个线程,同时获得同一个锁对象,但是双方都在等待对方先释放。

构造方法

java
// 返回一个锁对象
Lock lock = new ReentrantLock()
// 有参构造 参数 传入 一个布尔值,根据给定的公平政策创建一个锁对象。
Lock lock = new ReentrantLock(true)

方法

lock()

java
作用:上锁。
    
参数:无
    
返回值:无
    
示例:
// 创建锁对象 独占锁对象
Lock lock = new ReentrantLock(); 
lock.lock();

unlock()

java
作用:解锁。
    
参数:无
    
返回值:无
    
示例:
    
// 创建锁对象 独占锁对象
Lock lock = new ReentrantLock(); 
lock.unlock();

案例:使用锁的方式,实现抢票案例,使用多线程模拟:多人抢票 三个人 抢10张票。有一个线程买票 前边的线程购买完毕 后边的线程才能继续购买,多个线程必须排队买票 保证同一时间只能有一个线程买票 前边的线程购买完毕 后边的线程才能继续购买。

java
// 关键字代码块开始的地方 调用上锁的方法

// 这两个方法调用的区间的代码,只允许一个线程进行操作。

// 实际开发中 推荐将释放锁的操作 书写在finally中 表示任何情况都要释放锁
java
package com.lockPart;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 多人抢票 三个人 抢10张票
 * 有一个线程买票 前边的线程购买完毕 后边的线程才能继续购买
 */
public class BuyTicket implements Runnable{

    // 定义票数
    int ticketCount = 10;
    // 创建锁对象 独占锁对象
    Lock lock = new ReentrantLock();

    // 线程执行的区域
    @Override
    public void run() {
        // 循环遍历
        while (true){
            try {
                // 每个线程进来先休眠
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 然后把后续的代码上锁,只允许同时只有一个线程执行后续代码
                lock.lock();
                if (ticketCount == 0) {
                    break;
                }
                ticketCount--;
                System.out.println(Thread.currentThread().getName() + "抢到了,第" + (10 - ticketCount) +"张票,还剩余" + ticketCount + "张票");
            }finally {
                // 解锁
                lock.unlock();
            }
        }
        System.out.println("票卖完了");
    }
}
java
package com.lockPart;

public class TestLock {
    public static void main(String[] args) {
        // 创建Runnable实现类对象
        BuyTicket runnable = new BuyTicket();
        // 创建线程,并设置线程名称
        Thread th1 = new Thread(runnable,"Aqua");
        Thread th2 = new Thread(runnable,"Kanoco");
        Thread th3 = new Thread(runnable,"Reine");
        // 开启线程
        th1.start();
        th2.start();
        th3.start();
    }
}

Callable接口

是为了获得任务的返回结果对象,即 Callablecall() 方法返回的值。

背景:

java
为了解决创建线程时,重写run方法,不能够更改返回值类型,声明更多异常的问题。

**函数式接口:**接口中只有一个抽象方法,叫作函数式接口。

**作用:**Callable接口,是函数式接口。创建线程的第三种方式

**运行:**任何线程的执行最终都离不开Thread类。

使用案例:

java
package com.lockPart;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

// 设置泛型 为 Integer类型
public class TestCallable implements Callable<Integer> {

    // 声明返回值为 Integer类型
    @Override
    public Integer call() throws Exception {
        System.out.println("当前线程名称" + Thread.currentThread().getName());
        return 114514;
    }

    // 测试方法
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建Callable实现类对象,定义任务
        TestCallable testCallable = new TestCallable();
        // 创建FutureTask实现类对象,使用Callable接口实现类作为参数 构造实例,定义任务
        FutureTask<Integer> task = new FutureTask<Integer>(testCallable);
        // 创建线程对象 传入FutureTask 相当于传入Runnable实现类,定义线程
        Thread thread = new Thread(task, "线程A");
        // 线程就绪
        thread.start();
        // 获得任务的返回值
        Integer integer = task.get();
        System.out.println(integer); // 返回任务计算后的结果 114514
    }
}

对比实现Runnable接口的方式

image-20250806115039430

ts
举例:

你请人帮你办事(开一个线程):

如果只是让他干活,不关心他干得怎么样 —— 就用 Runnable。
如果你还要他回来告诉你结果 —— 那就用 Callable。

FutureTask类

返回带结果返回的任务类对象

java
FutureTask类实现了RunnableFuture接口
RunnableFuture接口实现了Runnable接口
Runnable接口实现了Thread类
    
所以FutureTask属于Runnable的实现类

**JUC:**JUC包属于线程中多并发的包,面试的难点。

构造方法:

java
// 参数是 Callable实现类对象,返回一个 FutureTask 任务对象
FutureTask(Callable<V> callable)

方法:

get()

java
作用:获取操作后的任务对象计算结果

参数:无
    
返回值:泛型类型对象
    
示例:
    
// 创建Callable实现类对象,定义任务
TestCallable testCallable = new TestCallable();
// 创建FutureTask实现类对象,使用Callable接口实现类作为参数 构造实例,定义任务
FutureTask<Integer> task = new FutureTask<Integer>(testCallable);
// 创建线程对象 传入FutureTask 相当于传入Runnable实现类,定义线程
Thread thread = new Thread(task, "线程A");
// 线程就绪
thread.start();
// 获得任务的返回值
Integer integer = task.get();
System.out.println(integer);

使用案例

java
package com.lockPart;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

// 设置泛型 为 Integer类型
public class TestCallable implements Callable<Integer> {

    // 声明返回值为 Integer类型
    @Override
    public Integer call() throws Exception {
        System.out.println("当前线程名称" + Thread.currentThread().getName());
        return 114514;
    }

    // 测试方法
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建Callable实现类对象,定义任务
        TestCallable testCallable = new TestCallable();
        // 创建FutureTask实现类对象,使用Callable接口实现类作为参数 构造实例,定义任务
        FutureTask<Integer> task = new FutureTask<Integer>(testCallable);
        // 创建线程对象 传入FutureTask 相当于传入Runnable实现类,定义线程
        Thread thread = new Thread(task, "线程A");
        // 线程就绪
        thread.start();
        // 获得任务的返回值
        Integer integer = task.get();
        System.out.println(integer); // 返回任务计算后的结果 114514
    }
}

线程池

背景:

java
回顾之前创建线程的三种方式,如果在多线程,多任务场景下
    1.不能统一的管理;
	2.会存在频繁的创建 以及 销毁的操作 浪费系统资源;
	3.不能执行定时任务;
    
以上效果,线程池都可以实现

方案:

java
线程池相当于一个存储管理多个线程对象的容器  使用统一的容器来保存 可以做到统一管理;
    
线程池中的线程对象 使用完毕 并不会立即回收 而是继续保存在线程池中 这样就避免了频繁创建销毁的操作;

Executors工具类

Executors 类是 Java 中的一个工具类,位于 java.util.concurrent 包中;

  • 它本身没有构造函数也不需要构造实例

  • 源码中的构造函数是 private 的;

它的主要作用是提供一些静态工厂方法,用来创建各种类型的线程池,比如:

java
ExecutorService executor = Executors.newFixedThreadPool(5);
ExecutorService executor = Executors.newCachedThreadPool();
ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(3);

构造方法

java

方法

newCachedThreadPool()

java
作用:用来创建一个 可缓存的线程池;创建一个 线程池,线程数量不固定,根据需要自动扩展或回收线程。重用空闲线程,如果线程空闲超过 60 秒,则会被回收。

特点:初始时没有核心线程。新任务来了,如果没有空闲线程,线程池会创建新线程。如果有空闲线程,则复用空闲线程执行任务。线程空闲时间超过 60 秒会被自动销毁,线程池自动缩减。
    
参数:无
    
返回值:ExecutorService类型线程池对象 
    
示例:
    
// 创建带缓存的线程池
ExecutorService es1 = Executors.newCachedThreadPool();

newFixedThreadPool(int nThreads)

java
作用:根据指定线程数创建线程池(静态方法)

参数:无
    
返回值:ExecutorService类型线程池对象 
    
示例:
    
// 创建线程 指定线程数为 10
ExecutorService es2 = Executors.newFixedThreadPool(10);

newScheduledThreadPool(int corePoolSize)

java
作用:根据指定线程数创建可以执行定时任务的线程池(静态方法)

参数:无
    
返回值:ScheduledExecutorService类型线程池对象 
    
示例:
    
// 创建线程 创建可以定时任务的线程
ScheduledExecutorService es3 = Executors.newScheduledThreadPool(5);

newSingleThreadExecutor()

java
作用:创建一个使用从无界队列运行的单个工作线程的执行程序,静态方法

参数:无
    
返回值:ExecutorService类型线程池对象 
    
示例:

每个方法的区别:

image-20250806173201499

使用案例

java
package com.lockPart;

import java.util.concurrent.*;

public class TestExcutors {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建带缓存的线程池
        ExecutorService es1 = Executors.newCachedThreadPool();
        // 创建线程 指定线程数为 10
        ExecutorService es2 = Executors.newFixedThreadPool(10);
        // 创建线程 创建可以定时任务的线程
        ScheduledExecutorService es3 = Executors.newScheduledThreadPool(5);
        // 创建线程池,单线程
        ExecutorService es4 = Executors.newSingleThreadExecutor();

        // 定义并提交任务,通过Runnable实现类,执行线程
        es1.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程池1:" + Thread.currentThread().getName()); // 线程池1:pool-1-thread-1
            }
        });
        // 提交任务,并新建线程1执行
        es2.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程池2:" + Thread.currentThread().getName()); // 线程池2:pool-2-thread-1
            }
        });
        // 提交任务,并新建线程2执行
        es2.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程池2:" + Thread.currentThread().getName()); // 线程池2:pool-2-thread-2
            }
        });
        // 提交任务,并设置定时任务执行,新建线程执行
        es3.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程池3:" + Thread.currentThread().getName()); // 延迟20秒后执行,线程池3:pool-3-thread-1
            }
        },20, TimeUnit.SECONDS);
        // 提交任务,创建线程
        es4.submit(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程池4:" + Thread.currentThread().getName()); // pool-4-thread-1 同一个线程执行
            }
        });
        Future<Integer> futureRes = es4.submit(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println("线程池4:" + Thread.currentThread().getName()); // pool-4-thread-1
                return 100;
            }
        });
        // 获取任务执行的结果
        Integer integer = futureRes.get();
        System.out.println("integer"+integer); // integer100
        // 提交任务,创建线程
        es4.execute(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程池4:" + Thread.currentThread().getName()); // pool-4-thread-1 同一个线程执行
            }
        });
        // 关闭线程池,有序关闭线程
        es4.shutdown();
    }
}

面试题

Executors工具类创建带缓存线程池的方法(newCachedThreadPool)中,源码

java
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

解析:
调用 ThreadPoolExecutor() 构造方法;
ThreadPoolExecutor 是一个实现类,继承Executor接口和ExecutorService接口;
返回一个 线程池对象;
    
构造方法传入的参数是 (corePoolSize 核心线程数,最多线程数,保持空闲时间,时间单位,线程队列)

Executor接口

Executor 接口是 Java 并发包(java.util.concurrent)中的核心接口之一,用来提供一种统一的任务执行机制,也就是:提交任务,不关心它是怎么执行的

解耦“任务的提交”和“任务的执行”

为什么要用 Executor

java
1、每次都创建一个新线程,资源浪费。
2、无法统一管理线程,比如线程池、线程复用、队列等。
3、不易扩展或维护。

它只有一个方法:

execute(Runnable command)

java
void execute(Runnable command)
    
作用:把任务交给某个线程去执行(怎么执行由具体实现决定)
    
参数:接收一个实现了 Runnable 接口的任务。

返回值:无
    
示例:
// 创建线程池,单线程
ExecutorService es4 = Executors.newSingleThreadExecutor();
    
 // 提交任务,创建线程
es4.execute(new Runnable() {
    @Override
    public void run() {
        System.out.println("线程池4:" + Thread.currentThread().getName());
    }
});

Runnable 是一个 接口,接口是不能直接实例化的,怎么能用 new Runnable() 呢?


这是 Java 的 匿名内部类(Anonymous Inner Class) 写法

你看到的这段代码:

java
es2.submit(new Runnable() {
    @Override
    public void run() {
        System.out.println("线程池2:" + Thread.currentThread().getName());
    }
});

其实是 Java 中的一种 简洁写法,它的意思是:

创建了一个实现了 Runnable 接口的 匿名类对象,并且重写了其中的 run() 方法。

这等价于这样一个完整的写法:

java
class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("线程池2:" + Thread.currentThread().getName());
    }
}

// 然后在代码中创建这个类的实例:
es2.submit(new MyRunnable());

ExecutorService接口

ExecutorService接口是Excutor接口的实现类。

**作用:**ExecutorService 是一个更加强大、可控制的线程池接口,用于提交任务、管理线程、关闭线程池等。

java
1. 提交任务(Runnable / Callable)
    可以提交两类任务:
        Runnable:不返回值
        Callable<V>:有返回值
    
2. 获取任务结果(通过 Future)
    submit() 方法返回 Future 对象,可以获取执行结果、检测是否完成、取消任务等。
    
3. 控制线程池的生命周期
    可以关闭线程池,防止资源泄露:
    executor.shutdown();              // 正常关闭
    executor.shutdownNow();          // 立即强制关闭

4. 批量提交任务  
    批量执行任务,并等待全部完成

方法

submit(Callable task)

java
作用:提交值返回任务以执行,并返回代表任务待处理结果的Future 

参数:Callable接口实现类

返回值:<T> Future<T>

示例:
// 创建线程池,单线程
ExecutorService es4 = Executors.newSingleThreadExecutor();
    
 Future<Integer> futureRes = es4.submit(new Callable<Integer>() {
    @Override
    public Integer call() throws Exception {
        System.out.println("线程池4:" + Thread.currentThread().getName()); // pool-4-thread-1
        return 100;
    }
});
// 获取任务执行的结果
Integer integer = futureRes.get();
System.out.println("integer"+integer); // integer100

submit(Runnable task)

java
作用:提交一个可运行的任务执行,并返回一个表示该任务的未来。默认线程保存60秒的线程  

参数:Runnable task 接口实现类 tash对象

返回值:无

示例:
    
// 创建线程池,单线程
ExecutorService es4 = Executors.newSingleThreadExecutor();
// 提交任务,创建线程
es4.submit(new Runnable() {
    @Override
    public void run() {
        // pool-4-thread-1 同一个线程执行
        System.out.println("线程池4:" + Thread.currentThread().getName());
    }
});

shutdown()

java
作用:关闭线程池,启动有序关闭,其中先前提交的任务将被执行,但不会接受任何新任务。  

参数:无

返回值:无

示例:
// 创建线程池,单线程
ExecutorService es4 = Executors.newSingleThreadExecutor();
    
// 关闭线程池,有序关闭线程
es4.shutdown();

execute()

java
作用:启动线程,使用父接口中的方法,重写方法

参数:Runnable接口的实现类 

返回值:无

示例:
    
// 创建线程池,单线程
ExecutorService es4 = Executors.newSingleThreadExecutor();
// 提交任务,创建线程
es4.execute(new Runnable() {
    @Override
    public void run() {
        // pool-4-thread-1 同一个线程执行
        System.out.println("线程池4:" + Thread.currentThread().getName());
    }
});

ScheduledExecutorService接口

ScheduledExecutorService 是 Java 并发包中继承自 ExecutorService 的接口,专门用于 支持任务的定时和周期性执行。线程池对象

构造方法

java

方法

schedule()

java
作用:延迟时间来执行任务;

参数:Runnable实现类(任务,同时新建线程执行),长度,时间单位
    Callable<V> callable, long delay, TimeUnit unit
    
TimeUnit:

DAYS 
时间单位代表二十四小时  
HOURS 
时间单位代表六十分钟  
MICROSECONDS 
时间单位代表千分之一毫秒  
MILLISECONDS 
时间单位为千分之一秒  
MINUTES 
时间单位代表60秒  
NANOSECONDS 
时间单位代表千分之一千分之一  
SECONDS 
时间单位代表一秒  

返回值:
    
示例:
// 创建线程 创建可以定时任务的线程
ScheduledExecutorService es3 = Executors.newScheduledThreadPool(5);    
// 提交任务,并设置定时任务执行,新建线程执行
es3.schedule(new Runnable() {
    @Override
    public void run() {
        // 延迟20秒后执行,线程池3:pool-3-thread-1
        System.out.println("线程池3:" + Thread.currentThread().getName()); 
    }
},20, TimeUnit.SECONDS);

单例模式线程安全

懒汉单例模式:存在线程安全问题;

我们可以使用同步关键字修饰方法解决 或者 修饰代码块解决;

案例:

java
package com.lockPart;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

// 不使用synchronized关键字,不同的线程获取到的对象不是同一个
// 使用synchronized关键字,不同的线程获取到的对象是同一个
public class TestSingleInstance {
    public static void main(String[] args) throws InterruptedException {
        // 创建线程池
        ExecutorService es1 = Executors.newFixedThreadPool(5);
        // 定义任务,创建线程1,提交执行
        es1.submit(new Runnable() {
            @Override
            public void run() {
                // 创建实例
                try {
                    SingleInstance single1 = SingleInstance.getSingleMode();
                    // 线程池1pool-1-thread-1获取单例对象com.lockPart.SingleInstance@3168743e
                    System.out.println("线程池1" + Thread.currentThread().getName() + "获取单例对象" + single1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        // 定义任务,创建线程1,提交执行
        es1.submit(new Runnable() {
            @Override
            public void run() {
                // 创建实例
                try {
                    SingleInstance single1 = SingleInstance.getSingleMode();
                    // 线程池1pool-1-thread-2获取单例对象com.lockPart.SingleInstance@51e03dd8
                    System.out.println("线程池1" + Thread.currentThread().getName() + "获取单例对象" + single1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        // 定义任务,创建线程1,提交执行
        es1.submit(new Runnable() {
            @Override
            public void run() {
                // 创建实例
                try {
                    SingleInstance single1 = SingleInstance.getSingleMode();
                    // 线程池1pool-1-thread-2获取单例对象com.lockPart.SingleInstance@51e03dd8
                    System.out.println("线程池1" + Thread.currentThread().getName() + "获取单例对象" + single1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        // 关闭线程池
        es1.shutdown();

    }
}
java
package com.lockPart;

public class SingleInstance {
    // 定义一个 私有静态 实例对象为空,先不new对象,懒汉单例
    private static SingleInstance instance = null;

    // 构造方法私有,才能保证内存中只有一份
    private SingleInstance(){}

    // 静态方法,获取一个实例
    public static synchronized SingleInstance getSingleMode() throws InterruptedException {
        if (instance == null){
            // 线程进来先休眠
            Thread.sleep(200);

            // 创建一个实例
            instance = new SingleInstance();
        }
        // 把这个实例返回
        return instance;
    }
}