Skip to main content

多线程

基本概念

程序概念

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

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

单核心配置

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

进程定义

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

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

线程定义

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

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

多线程

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

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

线程开辟和线程执行

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

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

并发并行

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

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

主线程

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

Thread类

  • java.lang.Thread 线程类

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

属性

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

构造方法

// 无参
Thread()

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

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

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

方法

getName()

作用:获取线程名称

参数:无

返回值:String类,

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

currentThread()

作用:(静态方法)获取当前线程对象

参数:无

返回值:Thread类型,线程对象

示例:
// 获取当前Thread类对象(线程对象)
Thread thread = Thread.currentThread();
// 打印线程名称
System.out.println("当前线程对象 " + thread); // Thread[main,5,main]

setName(String name)

作用:设置线程名称

参数: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()

作用:开启线程,向CPU提示自己准备就绪,可以被执行。

参数:无

返回值:无

示例:
// 创建 自定义线程对象
MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();

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

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

sleep()

作用:休眠进程,进入阻塞状态。

参数: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()

作用:设置线程的优先级,默认为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
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()

作用:打印线程的优先级,默认为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()

作用:线程插队,直到插队线程执行完毕。

参数:无

返回值:无

示例:
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)

作用:线程插队,指定插队时间,只允许在插队的时间优先执行,过了时间按照随机轮流执行。

参数:long 类型 500毫秒

返回值:无

示例:
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)

作用:线程插队,指定插队时间,只允许在插队的时间优先执行,过了时间按照随机轮流执行。

参数:long 类型 500毫秒,第二个参数是纳秒

返回值:无

示例:
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()

作用:线程礼让,当前线程向调度器发出信息,表示当前线程正在执行的线程愿意让步,但是调度器可以忽略这个信息。

参数:无

返回值:

注意:线程的礼让,可能会礼让不成功,但是插队是一定能够插队成功的。

示例:
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 + "次");
// }
}
}

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,这种方式来命名;

实现 Runnable 接口,并不是为了获得一个对象,而是为了定义线程要执行的任务。
在 Java 中,如果你创建一个类去实现 Runnable 接口,目的就是把这个类的 run() 方法作为线程要执行的逻辑。它没有返回值,也不能抛出受检异常,它的作用仅仅是:把你要让线程做的事情写在 run() 方法里。


继承 Thread 的类,其作用是:
把你要执行的任务封装在 run() 方法中,并通过线程对象直接启动。是直接把你的“任务”写在了线程类的 run() 方法中,然后通过 start() 启动线程。这个类的作用就是:定义线程要执行的任务(写在 run() 里),并直接成为一个线程对象,可以被 .start() 启动。你定义的类是一个线程本身,它继承了 Thread,可以直接拿来运行。

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

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 + "次");
}
}
}

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

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实例
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实现类作为参数的构造方法创建实例

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();
}
}

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

两种方式的对比

image-20250806115416880

线程状态

image-20250801103243194

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

线程优先级

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

优先级示例

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() + "线程执行完毕");
// 线程挂了
}
}

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();

}
}

线程案例

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

需求:
每个线程代表一个人;
可设置每人爬山速度;
每爬完100米显示信息;
爬到终点时给出相应提示;
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();
}
}

模拟叫号看病案例

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

分析:子线程作为特需号类 主线程作为普通号类
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(同步)关键字

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

基本使用

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

}


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

}

使用背景

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

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

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

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来创建线程,同时设置同步关键字,修饰代码块,保证线程安全。

  • 同步代码块锁定的范围更加精确
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来创建线程,同时设置同步关键字,修饰方法,保证线程安全。

  • 可以用于修饰方法 ,表示同一时间只能有一个线程访问这个方法
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。

// 同步锁的是当前这个对象的访问权限,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()

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()

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();
}
}

生产者消费者案例:

电脑类

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 +
'}';
}
}

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

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();
}
}
}
}

消费者类

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();
}
}
}
}

测试类

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;阻塞表示队列是有长度限制的,所以当队列满了以后,将不能再添加新的数据到队列中;

构造方法

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

方法

add()

作用:在插入此队列的尾部,如果有可能立即这样做不超过该队列的容量,返回指定的元素。

参数:无

返回值:无

示例:

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

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

take()

作用:从队列中取出数据。

参数:无

返回值:返回指定的元素

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

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

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

电脑类

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 + '\'' +
'}';
}
}

生产者类

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);
}
}
}
}

消费者类

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();
}
}
}
}

测试类

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的实现原理:

例如 a = a + 1 ; 这个操作;

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

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

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

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

ReentrantLock类

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

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

构造方法

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

方法

lock()

作用:上锁。

参数:无

返回值:无

示例:
// 创建锁对象 独占锁对象
Lock lock = new ReentrantLock();
lock.lock();

unlock()

作用:解锁。

参数:无

返回值:无

示例:

// 创建锁对象 独占锁对象
Lock lock = new ReentrantLock();
lock.unlock();

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

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

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

// 实际开发中 推荐将释放锁的操作 书写在finally中 表示任何情况都要释放锁
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("票卖完了");
}
}

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() 方法返回的值。

背景:

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

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

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

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

使用案例:

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

举例:

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

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

FutureTask类

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

FutureTask类实现了RunnableFuture接口
RunnableFuture接口实现了Runnable接口
Runnable接口实现了Thread类

所以FutureTask属于Runnable的实现类

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

构造方法:

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

方法:

get()

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

参数:无

返回值:泛型类型对象

示例:

// 创建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);

使用案例

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
}
}

线程池

背景:

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

以上效果,线程池都可以实现

方案:

线程池相当于一个存储管理多个线程对象的容器  使用统一的容器来保存 可以做到统一管理;

线程池中的线程对象 使用完毕 并不会立即回收 而是继续保存在线程池中 这样就避免了频繁创建销毁的操作;


Executors工具类

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

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

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

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

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

构造方法


方法

newCachedThreadPool()

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

特点:初始时没有核心线程。新任务来了,如果没有空闲线程,线程池会创建新线程。如果有空闲线程,则复用空闲线程执行任务。线程空闲时间超过 60 秒会被自动销毁,线程池自动缩减。

参数:无

返回值:ExecutorService类型线程池对象

示例:

// 创建带缓存的线程池
ExecutorService es1 = Executors.newCachedThreadPool();

newFixedThreadPool(int nThreads)

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

参数:无

返回值:ExecutorService类型线程池对象

示例:

// 创建线程 指定线程数为 10
ExecutorService es2 = Executors.newFixedThreadPool(10);

newScheduledThreadPool(int corePoolSize)

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

参数:无

返回值:ScheduledExecutorService类型线程池对象

示例:

// 创建线程 创建可以定时任务的线程
ScheduledExecutorService es3 = Executors.newScheduledThreadPool(5);

newSingleThreadExecutor()

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

参数:无

返回值:ExecutorService类型线程池对象

示例:

每个方法的区别:

image-20250806173201499

使用案例

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)中,源码

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

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

它只有一个方法:

execute(Runnable command)

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) 写法

你看到的这段代码:

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

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

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

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

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

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

ExecutorService接口

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

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

1. 提交任务(Runnable / Callable)
可以提交两类任务:
Runnable:不返回值
Callable<V>:有返回值

2. 获取任务结果(通过 Future)
submit() 方法返回 Future 对象,可以获取执行结果、检测是否完成、取消任务等。

3. 控制线程池的生命周期
可以关闭线程池,防止资源泄露:
executor.shutdown(); // 正常关闭
executor.shutdownNow(); // 立即强制关闭

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

方法

submit(Callable task)

作用:提交值返回任务以执行,并返回代表任务待处理结果的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)

作用:提交一个可运行的任务执行,并返回一个表示该任务的未来。默认线程保存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()

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

参数:无

返回值:无

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

// 关闭线程池,有序关闭线程
es4.shutdown();

execute()

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

参数: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 的接口,专门用于 支持任务的定时和周期性执行。线程池对象

构造方法


方法

schedule()

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

参数: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);

单例模式线程安全

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

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

案例:

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();

}
}

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;
}
}