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 | class MyThread extends Thread { |
特点
写法简单
线程类已经继承了
Thread,无法再继承别的类任务和线程对象耦合在一起
2. 实现 Runnable 接口
把任务写进 run(),再交给 Thread
1 | class MyRunnable implements Runnable { |
特点
更常用
任务和线程分离了,设计更合理
类还可以继续继承别的父类
run()没有返回值,也不能直接抛出检查异常
3. 实现 Callable 接口
配合 FutureTask 使用,可以有返回值
1 | import java.util.concurrent.Callable; |
特点
有返回值
可以抛异常
需要借助
FutureTask或线程池get()会阻塞等待结果
4. 线程池创建线程
通过 ExecutorService 管理线程
1 | import java.util.concurrent.ExecutorService; |
特点
实际开发最推荐
线程可以复用,减少频繁创建和销毁开销
便于统一管理线程数量、任务队列、拒绝策略等
更适合高并发场景
区别与联系
联系:真正启动线程还得靠 Thread,它才是真正的执行载体。
Runnable 和 Callable
实现 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。
code test
如何让t1、t2、t3有序地执行
1 | Thread t1 = new Thread(sout("666")); |
如何停止一个正运行的线程
标记flag停止
stop强行终止
interrupt方法
//TODO 缺代码, ai不上后删除此句