JAVA EE初阶 2: 多线程-初阶(重要)


由于单核CPU性能瓶颈和IO等待场景,并发编程成为充分利用多核资源、提升CPU利用率的刚需。
进程 (Process)与线程(Thread)
进程是操作系统资源分配的基本单位,有独立的内存空间、文件句柄等系统资源;创建和销毁的代价较高。
线程是操作系统调度执行的基本单位(CPU以线程为单位进行调度),因此被称为“轻量级进程”。
进程与线程的关系
一个进程至少包含一个线程(称为主线程);同一进程内的多个线程共享该进程的内存空间等资源;不同进程之间内存不共享,各自拥有独立的运行环境。
崩溃影响:一个进程崩溃通常不影响其他进程,但一个线程崩溃可能导致整个进程崩溃。
创建开销:创建进程时,系统分配内存等进程级资源的同时也会创建第一个线程;在已有进程内再创建新线程时,无需重复申请进程资源,只需分配私有栈空间等少量资源,开销远小于进程。
应用价值:线程以极低的额外开销提供并发能力,使服务器可以同时处理大量客户端请求;正好适配 7×24 小时不间断运行、随时面对未知数量客户端的高并发服务场景。

为进一步提升效率,又引入了线程池和协程。
多线程
每个线程都是独立的执行流,能够独立执行自身的指令序列,并且多个线程可以同时执行不同的代码;多线程技术将一个任务分解为多个子任务,由不同线程分别处理,其中负责启动其他线程的线程称为主线程。
多进程需为每个进程独立申请资源,开销较大;多线程共享同一进程的资源,更加轻量。
增加线程在CPU核心数充足时可提升效率,但线程数超过核心数后,调度开销剧增,反使性能下降。
多个线程操作共享资源(如内存中同一个变量)可能产生冲突,引发 bug;一个线程抛出未捕获(catch)的异常,整个进程(包括所有线程)都会终止;而多进程中一个进程崩溃不会影响其他进程。
多线程编程须合理控制线程数量,并妥善处理同步与异常。

线程休眠
Thread.sleep() 静态方法,通过类名.方法名调用;让当前正在执行的线程暂停指定的毫秒数;线程休眠时不会释放它所持有的监视器锁。
注:会抛出 InterruptedException,属于受查异常(Checked Exception),必须显式处理:使用 throws 声明抛出 或 使用 try-catch 捕获。
1.创建线程
Java 标准库中的Thread类是对操作系统线程 API(如 Linux 的 pthread)的封装;JVM 重点封装了多线程能力,从而屏蔽了底层系统的差异。
1.1 继承Thread类
定义一个继承自 Thread 的内部类,重写其 run() 方法,run() 是线程的入口方法,用来描述线程要执行的任务逻辑;
创建该内部类的实例,利用向上转型将其赋值给 Thread 类型的引用(多态),最后调用 start() 方法启动线程。
public class Demo {
static class MyThread extends thread {
@Override
public void run(){
System.out.println("Hello Thread");
}
}
public static void main(String[] args) {
// 创建线程对象
Thread thread = new MyThread();
// 调用start方法启动线程
thread.start();
System.out.println("Hello main");
}
}
通过while(true) 循环持续打印,可以直观对比两种输出差异;为避免循环打印过快,可以添加 sleep 方法让程序短暂休眠,但需要显示处理异常。

main方法中的sleep 既可以通过 throws 声明抛出,也可以用 try-catch 捕获;
子类重写 run() 方法中,因父类 Thread 的 run() 没有声明任何异常,子类须遵循异常声明一致规则,所以不能使用 throws,只能用try-catch捕获,否则会编译错误。
public class Demo {
static class MyThread extends Thread {
@Override
public void run(){
while(true){
System.out.println("Hello thread");
try {
Thread.sleep(1000);
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException{
// 创建线程对象
Thread thread = new MyThread();
// 调用start方法启动线程 并发执行
thread.start();
// 仅调用run()方法体 单线程顺序执行
//thread.run();
while(true){
System.out.println("Hello main");
Thread.sleep(1000);
}
}
}
每个进程至少有一个线程(称为主线程),Java 程序中的 main 方法就运行在至少存在的主线程中。
调用线程对象的 start() 方法会通过操作系统 API ,在底层创建真正的新线程,以 run() 为入口与与主线程并发执行,两者输出可能交替出现;
而直接调用 run() 则等同于当前线程(如主线程)中顺序执行普通方法,不会创建新线程;若 run() 内包含死循环,主线程将被永久阻塞,后续代码 hello main 无法执行。

可通过 Ctrl+C 终止程序;线程调度由操作系统决定,顺序无序且不可预测;即便并发执行中两个线程都调用Thread.sleep(1000) ,但休眠结束后哪个线程先执行仍不确定,因此输出结果并非严格交替。



1.2 实现Runnable接口

总结



1.3 匿名内部类




1.4 lambda表达式(常用)



2.Thread类及常见方法
2.1 构造方法



2.2 常见属性




2.3 启动一个线程 start()

调用start方法,才真的在操作系统的底层创建出一个线程。


2.4 中断一个线程


package thread;
public class Demo7 {
private 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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
t.start();
System.out.println("hello main");
Thread.sleep(3000);
flag = false;
System.out.println("让 t 线程终止");
}
}


package thread;
public class Demo7 {
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) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
});
t.start();
System.out.println("hello main");
Thread.sleep(3000);
t.interrupt();
System.out.println("t 线程的中断标志位:" + t.isInterrupted());
}
}







2.5 等待一个线程 join()
有时,我们需要等待一个线程完成它的工作后,才能进的下⼀步工作,这时我们需要⼀个方法明确等待线程的结束。

主线程等待代码
package thread;
public class Demo7 {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for(int i = 0; i < 5; i++){
System.out.println("hello thread");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
//e.printStackTrace();
break;
}
}
});
System.out.println("主线程开始");
t.start();
System.out.println("主线程等待之前");
t.join();
System.out.println("主线程等待之后");
System.out.println("主线程结束");
}
}
t 线程等待代码
package thread;
public class Demo7 {
public static void main(String[] args) throws InterruptedException {
Thread mainThread = Thread.currentThread();
Thread t = new Thread(() -> {
try {
System.out.println("t 线程等待之前");
mainThread.join();
System.out.println("t 线程等待之后");
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
});
t.start();
for(int i = 0; i < 3; i++){
System.out.println("hello main");
Thread.sleep(1000);
}
}
}




2.6 获取当前线程引用

2.7 休眠当前线程


3.线程的状态
线程的状态是⼀个枚举类型 Thread.State




4.多线程带来的的风险-线程安全(重点!)
4.1 概念 例子



4.2 线程不安全的原因











5.死锁(重点)






Object lock1 = new Object();
Object lock2 = new Object();
Thread t1 = new Thread() {
@Override
public void run() {
synchronized (lock1) {
synchronized (lock2) {
// do something...
}
}
}
};
t1.start();
Thread t2 = new Thread() {
@Override
public void run() {
synchronized (lock2) {
synchronized (lock1) {
// do something...
}
}
}
};
t2.start();
不会产生环路等待的代码:约定好先获取lock1,再获取lock2,就不会环路等待。
Object lock1 = new Object();
Object lock2 = new Object();
Thread t1 = new Thread() {
@Override
public void run() {
synchronized (lock1) {
synchronized (lock2) {
// do something...
}
}
}
};
t1.start();
Thread t2 = new Thread() {
@Override
public void run() {
synchronized (lock1) {
synchronized (lock2) {
// do something...
}
}
}
};
t2.start();
6.synchronized关键字 - 监视器锁(monitor lock)






6.1 synchronized的特性









6.2 synchronized使用示例
重点是第一种,也是最常用的。


6.3 Java标准库中的线程安全类


7.wait和notify



7.1 wait()方法





7.2 notify() notifyAll() 方法

![]()



7.3 wait和sleep的对比(面试题)


8.多线程案例
8.1 单例模式(面试常考)
单例模式是校招中最常考的设计模式之一











8.2 阻塞队列














8.3 线程池
(1)概念
线程池最大的好处就是减少每次启动、销毁线程的损耗。








(2)模拟实现



8.4 定时器


自行实现一个定时器





虽然执行结果看起来对了,但其实还是有几个问题的。




9.总结


更多推荐




所有评论(0)