线程
# 什么是线程安全?
对于共享资源,多个线程同时访问,都能保证资源的正确性和一致性;
# 线程&进程
线程:CPU调度执行最小单元;
进程:操作系统分配资源的最小单元;
在java中,一台JVM实例就是一个进程,执行main函数就是开启一个线程;
线程是进程中一条执行流程,同一个进程中多个线程共享地址空间和文件等资源,每个线程有各自的寄存器和栈;
# 为什么需要线程?
线程创建时间,终止时间比进程快;
进程在创建过程中,需要资源管理信息,如:内存管理信息,文件管理信息;
线程创建过程中,只需要共享这些资源信息;
线程切换比进程切换快;
线程具有相同的地址空间(虚拟内存共享),同一个进程的线程都具有同一个页表,切换的时候不需要切换页表;
线程之间数据交互效率高;
同一进程各线程间共享内存和文件资源,线程之间数据传递时,不需要经过内核;
# 创建线程方式
- Thread方式
public static void main(String[] args) {
new Thread(()->{
System.out.println("线程开始");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程结束");
}).start();
}
2
3
4
5
6
7
8
9
10
11
- Runnable配合Thread
线程与任务分开,更容易与线程池等高级API配合,更加灵活
public static void main(String[] args) {
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("线程开始");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程结束");
}
};
Thread thread = new Thread(runnable);
thread.start();
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
- FutureTask
接受Callable类型的参数,处理有返回结果的情况
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<Integer> task = new FutureTask<>(() -> {
System.out.println("线程运行后");
return 100;
});
new Thread(task).start();
Integer res = task.get();
System.out.println("线程得到结果:" +res);
}
2
3
4
5
6
7
8
9
# 线程状态
- NEW
- RUNNABLE
- BLOCKED
- WAITING
- TIME_WAITING
- TERMINATED
# Thread#start()和run()方法区别?
start:启动一个新线程并异步执行其中的任务;
run():执行当前线程;
# Thread相关接口/类
函数式接口:
Runnable:抽象方法run(),无返回值
Callable:call(),有返回值
接口:
Future:cancel(),get()等方法;
类:
FutureTask:异步得到结果的任务
ExecutorService.submit()的返回类型;
实现了Future&Runnable接口,可以作为任务直接被执行,也可以给交给Executor执行;
构造函数可以传Runnable/Callable,传入Runnable也会转化为Callable;相当于对Callable进行封装,管理任务执行情况,存储了call方法的任务执行结果
# sleep&wait
sleep不释放锁,释放CPU;
wait释放锁,释放CPU;
# 线程死锁
条件:
互斥条件;
多个线程不能使用同一个资源;
持有并等待条件;
当线程 A 已经持有了资源 1,又想申请资源 2,而资源 2 已经被线程 B持有了,所以线程 A 就会处于等待状态;
线程 A 在等待时不会释放自己的资源;
不可剥夺条件;
线程持有的资源在使用期间不能被其它线程获取;
环路等待条件;
线程 A 已经持有资源 2,而想请求资源 1, 线程 B 已经获取了资源 1,而想请求资源 2,这就形成资源请求等待的环形图;
死锁检测:
jvisualvm,的线程dump;
避免死锁:
顺序加锁;
请求锁时限/失败返回
reentrantLock的tryLock
# 如何设置线程数量?
- CPU密集型任务(N+1):多出来的一个线程防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响,一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间;
- IO密集型(2N):线程处理IO的时间段不会占用CPU,这时可以将CPU交出给其它线程使用;