多线程初级
什么是线程Thread
线程(Thread) 是操作系统能够进行调度的最小执行单元。可以把它理解为程序内部的一条独立执行路径
通俗理解(银行办业务):
假设一家公司要去银行办理业务,既要财务转账,又要福利发放,还得缴社保:
单线程模式:只有张三一个会计,他需要一个人依次完成三件事,耗时很长。
多线程模式:张三叫来李四、王五一起帮忙。三个人分别负责一件事,各自排队办理。三个人都是独立的“执行流”,共同为这家公司服务。
张三就是主线程(Main Thread)
李四、王五就是新创建的子线程
三个执行流并发执行,提高了整体效率
为什么需要多线程
① 充分利用多核CPU
单核CPU的发展已遇瓶颈,现在都是多核CPU。多线程能充分利用多核资源,提高程序运行效率。
② 处理IO等待
当程序需要等待IO操作(如网络请求、文件读写)时,CPU可以切换到其他线程执行任务,避免CPU空闲等待。
③线程比进程更轻量,调度开销和资源占用都更少。
线程和进程的区别
进程是包含线程的,每个进程最少有一个线程存在即主线程。
进程和进程之间补共不内存空间,同一个进程的线程之间共享内存空间。
进程是系统分配资源的最小空间,线程是系统调度的最小单位。

Java线程和操作系统线程的关系
线程是操作系统的概念。操作系统内核实现了线程机制,并提供API供用户使用(如Linux的pthread库)。
Java中的Thread类是对操作系统线程API的进一步抽象和封装,让我们可以跨平台地使用多线程。
创建线程的四种方法
1.继承Thread类来创建线程类


2.实现Runnable接口


3.匿名内部类创建Thread子类对象(或Runnable子类对象)


4.lambda表达式创建Runnable子类对象


注意:Lambda表达式要求接口是函数式接口(只有一个抽象方法),Runnable正好满足。
多线程的优势:提升运行速度

Thread类核心方法详解
1.常见构造方法

2.常见属性


3.启动线程

Thread t = new MyThread();
t.start(); // ✅ 在操作系统创建新线程,并执行run()
4.中断线程
一般有两种方法:用共享的标记进行沟通(添加volatile关键字)
public class ThreadDemo {
private static class MyRunnable implements Runnable {
public volatile boolean isQuit = false; // volatile保证可见性
@Override
public void run() {
while (!isQuit) {
System.out.println("线程工作中...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("线程结束");
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread thread = new Thread(target);
thread.start();
Thread.sleep(5000); // 5秒后通知停止
target.isQuit = true; // 修改标志位
}
}
第二种是使用interrupt()方法
public class ThreadDemo {
private static class MyRunnable implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()) {
System.out.println("线程工作中...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// sleep被中断,清除中断标志,需要手动退出
System.out.println("收到中断信号,退出");
break;
}
}
System.out.println("线程结束");
}
}
public static void main(String[] args) throws InterruptedException {
MyRunnable target = new MyRunnable();
Thread thread = new Thread(target);
thread.start();
Thread.sleep(5000);
thread.interrupt(); // 线程对象调用interrupt方法,发送中断信号
}
}


两种中断方式的区别

5.等待线程结束
当主线程需要等待子线程执行完毕后再继续时,使用join()。
public class ThreadDemo {
public static void main(String[] args) throws InterruptedException {
Runnable target = () -> {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + "工作中...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "结束");
};
Thread t1 = new Thread(target, "李四");
Thread t2 = new Thread(target, "王五");
System.out.println("先让李四工作");
t1.start();
t1.join(); // 主线程等待李四完成
System.out.println("李四结束了,再让王五工作");
t2.start();
t2.join(); // 主线程等待王五完成
System.out.println("都结束了");
}
}
6.获取当前线程引用
Thread.currentThread()
Thread current = Thread.currentThread();
System.out.println("当前线程名:" + current.getName());
7.线程休眠
sleep()
// 休眠3秒
Thread.sleep(3000);
// 更精确的休眠(纳秒级,但实际精度取决于操作系统)
Thread.sleep(3000, 500000);
注意:sleep()只能保证休眠时间不少于指定时间,实际唤醒时间取决于操作系统的线程调度。
线程状态
线程的状态是⼀个枚举类型Thread.State
NEW:安排了⼯作,还未开始⾏动
RUNNABLE:可⼯作的,⼜可以分成正在⼯作中和即将开始⼯作.
BLOCKED:这⼏个都表⽰排队等着其他事情• WAITING; TIMED_WAITING
TERMINATED:⼯作完成了.

状态转移

线程安全
如果多线程环境下代码运⾏的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是 线程安全的。
① 线程调度是抢占式的(随机性)
操作系统调度线程的顺序是不确定的,程序员必须保证在任意执行顺序下代码都能正常工作。
② 多个线程修改共享数据
上面的例子中,count是多个线程共享的变量。多个线程同时修改它,会导致数据不一致。
③ 操作不具备原子性
count++; //
看起来是一行代码,实际是三步操作:
从内存读取count的值到CPU寄存器
CPU对值进行+1运算
将结果写回内存
如果线程A执行完第1步后被切换,线程B修改了count,然后线程A继续执行第2、3步,就会覆盖B的修改,导致数据丢失
Java内存模型(JMM)
⽬的是屏蔽掉各种硬件和操作系统的内存访问差异,以实现让Java程序在各种平台下都能达到⼀致的 并发效果.
线程之间的共享变量存在 主内存 (Main Memory).
每⼀个线程都有⾃⼰的 "⼯作内存" (Working Memory).
当线程要读取⼀个共享变量的时候,会先把变量从主内存拷⻉到⼯作内存,再从⼯作内存读取数据.
当线程要修改⼀个共享变量的时候,也会先修改⼯作内存中的副本,再同步回主内存
synchronized详解
synchronized特性:
1.互斥性
synchronized会起到互斥效果,某个线程执⾏到某个对象的synchronized中时,其他线程如果也执⾏ 到同⼀个对象synchronized就会阻塞等待.
进⼊synchronized修饰的代码块,相当于加锁
退出synchronized修饰的代码块,相当于解锁
类比:厕所门上的“有人/无人”标识:
无人 → 可以进入,设置为“有人”
有人 → 其他人只能在门外排队等待
public class ThreadSafe {
private static int count = 0;
private static final Object locker = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
synchronized (locker) {
count++;
}
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
synchronized (locker) {
count++;
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("count: " + count); // 稳定输出100000
}
}
2.可重入性
同一个线程可以多次获取同一把锁,不会死锁。
synchronized (locker) {
synchronized (locker) { // 同一线程再次获取锁,允许!
count++;
}
}
在可重⼊锁的内部,包含了"线程持有者"和"计数器"两个信息.
实现原理:每个锁内部记录持有线程和计数器:
-
加锁时,如果是同一个线程,计数器+1
-
解锁时,计数器-1,减到0时才真正释放锁

public class SynchronizedDemo {
private final Object locker = new Object();
// 1. 锁指定对象
public void method1() {
synchronized (locker) {
// 业务代码
}
}
// 2. 锁当前对象 (this)
public synchronized void method2() {
// 业务代码
}
// 3. 锁类的Class对象
public synchronized static void method3() {
// 业务代码
}
}
线程安全类
String是不可变类,本身线程安全
StringBuffer内部方法加了synchronized,但已不推荐使用,单线程用StringBuilder,多线程用其他方式同步

volatile关键字
volatile修饰的变量,能够保证"内存可⻅性".
前⾯我们讨论内存可⻅性时说了,直接访问⼯作内存(实际是CPU的寄存器或者CPU的缓存),速度⾮ 常快,但是可能出现数据不⼀致的情况.
volatile修饰的变量,读写操作直接在主内存进行,不经过工作内存(CPU缓存)
static class Counter {
public volatile int flag = 0;
}
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
while (counter.flag == 0) {
// 循环等待
}
System.out.println("循环结束!");
});
Thread t2 = new Thread(() -> {
Scanner scanner = new Scanner(System.in);
System.out.println("输入一个整数:");
counter.flag = scanner.nextInt(); // 用户输入非0时,t1结束
});
t1.start();
t2.start();
}
如果flag不加volatile,t1可能永远看不到t2对flag的修改,导致死循环。加上volatile后,t1每次都能读到主内存中的最新值。

wait()和notify():线程间的通信
wait()的作用
释放当前锁,让出CPU
线程进入等待状态
其他线程调用notify()或notifyAll()后被唤醒
wait要搭配synchronized来使⽤.脱离synchronized使⽤wait会直接抛出异常.

notify()的作用
唤醒一个正在等待该锁的线程
被唤醒的线程需要重新竞争锁

public class WaitNotifyDemo {
public static void main(String[] args) throws InterruptedException {
Object locker = new Object();
// 等待线程
Thread t1 = new Thread(() -> {
synchronized (locker) {
try {
System.out.println("等待中...");
locker.wait(); // 释放锁,进入等待
System.out.println("被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 唤醒线程
Thread t2 = new Thread(() -> {
synchronized (locker) {
System.out.println("准备唤醒");
locker.notify(); // 唤醒等待中的线程
System.out.println("已唤醒");
}
});
t1.start();
Thread.sleep(1000);
t2.start();
}
}

notify():随机唤醒一个等待线程
notifyAll():唤醒所有等待线程(它们需要竞争锁)

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)