JUC - 多线程

线程基础

文题

线程&进程 区别

进程是资源分配单位,线程是调度执行单位;
进程独立,线程共享进程资源;进程切换重,线程切换轻。

1. 定义不同
进程是资源分配的基本单位,线程是CPU 调度的基本单位

2. 包含关系不同
一个进程里可以有多个线程,线程必须依附于进程存在。

3. 资源共享不同
进程之间内存空间独立,互不共享。
同一进程内的线程共享堆、方法区等资源,但每个线程也有自己的程序计数器、虚拟机栈、本地方法栈

4. 开销不同
进程创建和切换开销更大。
线程创建和切换开销更小,所以并发执行效率通常更高。

5. 通信方式不同
进程通信比较复杂,需要管道、消息队列、socket 等 IPC 机制。
线程通信更简单,因为它们天然共享进程内存,但也更容易出现线程安全问题。

并发&并行 区别

并发是多个任务在同一时间段内交替执行,强调的是同时处理的能力。
并行是多个任务在同一时刻同时执行,强调的是同时执行的能力。
单核 CPU 更偏向并发,多核 CPU 才能更好实现并行。

notify & notifyAll区别

notify随机唤醒一个正在等待的线程
notifyAll唤醒所有正在等待的线程

sleep()&wait()区别

sleep 是 Thread 的静态方法,wait 是 Object 的成员方法。
sleep 不释放锁,wait 会释放锁。
sleep 可以在任何地方调用,wait 必须在 synchronized 中调用。
sleep 主要用于暂停线程,wait 主要用于线程间通信。
从状态上看,sleep 进入 TIMED_WAITING,wait() 进入 WAITING。

线程

创建方式

1. 继承 Thread

重写 run() 方法,然后调用 start()

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyThread extends Thread {
@Override
public void run() {
System.out.println("线程执行了");
}
}

public class Test {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
}
特点
  • 写法简单

  • 线程类已经继承了 Thread,无法再继承别的类

  • 任务和线程对象耦合在一起


2. 实现 Runnable 接口

把任务写进 run(),再交给 Thread

1
2
3
4
5
6
7
8
9
10
11
12
13
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("线程执行了");
}
}

public class Test {
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
}
}
特点
  • 更常用

  • 任务和线程分离了,设计更合理

  • 类还可以继续继承别的父类

  • run() 没有返回值,也不能直接抛出检查异常


3. 实现 Callable 接口

配合 FutureTask 使用,可以有返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 100;
}
}

public class Test {
public static void main(String[] args) throws Exception {
FutureTask<Integer> futureTask = new FutureTask<>(new MyCallable());
Thread t = new Thread(futureTask);
t.start();

Integer result = futureTask.get();
System.out.println(result);
}
}
特点
  • 有返回值

  • 可以抛异常

  • 需要借助 FutureTask 或线程池

  • get() 会阻塞等待结果


4. 线程池创建线程

通过 ExecutorService 管理线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Test {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(3);

pool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 执行任务");
});

pool.shutdown();
}
}
特点
  • 实际开发最推荐

  • 线程可以复用,减少频繁创建和销毁开销

  • 便于统一管理线程数量、任务队列、拒绝策略等

  • 更适合高并发场景

区别与联系

联系:真正启动线程还得靠 Thread,它才是真正的执行载体。

RunnableCallable

实现 Runnable
  • 任务和线程解耦

  • 无返回值

  • 更符合面向对象设计

  • 不可抛异常

实现 Callable
  • Runnable 更强

  • 有返回值,可抛异常

start()run()

run()

只是一个普通方法调用,谁调用它,谁就执行。
不会启动新线程

start()

才是真正用于启动新线程的方法。
调用 start() 后,线程进入可运行状态,由 JVM 调度,然后由新线程自动执行 run()

线程状态

NEW 是新建,start 后进入 RUNNABLE。
抢不到 synchronized 锁进入 BLOCKED。
wait、join、park 进入 WAITING。
sleep、wait(long)、join(long) 进入 TIMED_WAITING。
执行结束进入 TERMINATED。
其中 Java 没有单独的 RUNNING 状态,运行中也属于 RUNNABLE。

Pasted image 20260317200038

code test

如何让t1、t2、t3有序地执行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Thread  t1 = new Thread(sout("666"));

Thread t2 = new Thread(()->{
try{
t1.join(); //只有线程t1执行完才跑t2
} catch (InterruptedException e){
e.printStackTrace();
}
sout(t2)
})

Thread t3 = new Thread(()->{
try{
t2.join(); //只有线程t2执行完才跑t3
} catch (InterruptedException e){
e.printStackTrace();
}
sout(t3);
})

t1.start();
t2.start();
t3.start();

如何停止一个正运行的线程

标记flag停止

stop强行终止

interrupt方法

Pasted image 20260317214814 //TODO 缺代码, ai不上后删除此句

线程安全