4.1 线程与进程
线程是程序执行的最小单元,一个进程可以拥有多个线程,各个线程之间共享程序的内存空间以及一些进程级资源,但拥有自己的栈空间。
4.3 Java 多线程
方法一:继承 Thread 类,重写 run 方法;
方法二:实现 Runnable 接口,并实现 run 方法;
方法三:实现 Callable 接口,重写 call 方法;
方法四:使用线程池;
4.4 run 和 start 区别
start 方法启动线程后,线程为就绪状态,JVM通过调用线程的 run 方法完成实际的操作。run 方法结束后线程终止。
直接调用 run 方法只会作为普通的函数调用,程序中仍然只有主线程一个线程。
4.5 多线程同步
方法一:Synchronized 关键字,保证同一时间仅有一个线程访问;
方法二:JDK5 新增了 Lock 接口
4.6 Lock
ReentrantLock:重入锁,是指在同一线程中,外部方法获得锁后,内部方法仍然可以回去该锁,若锁不具有可重入性会导致死锁。其持有的是对象监视器。借助 Condition 可以实现等待 / 通知模型。
ReentrantReadWriteLock:将锁分为读锁和写锁,读锁可以在没有写锁的时候被多个线程持有,只有写锁是独占的。如果写锁被一个线程占用,其他线程无论是想要获取读锁还是写锁,都必须等待写锁被释放。
4.7 synchronized 与 lock 的异同
1. synchronized 是托管给 jvm 执行的,lock 的锁定是代码控制的。
2. 资源竞争不激烈的情况下 synchronized 性能更优;资源竞争激烈时 synchronized 性能降低较多。
3. Lock 需要手动控制锁的释放。
4. synchronized 修饰方法时,静态 static 方法持有的是类锁,非静态方法持有的是对象锁。
4.8 sleep 和 wait 的区别
1. sleep 是用来控制自身流程的,wait 用于线程间的通讯。
2. sleep 不释放锁;wait 释放锁;
4.8 补充 - yield
Thread.yield()
方法可以让当前线程放弃当前的CPU时间片,但这并不意味着当前线程会立即停止执行。实际上,yield
方法只是提示线程调度器(Thread Scheduler),当前线程愿意让出对CPU的使用,但是否真的会立即让出CPU时间片,取决于线程调度器的实现和当前的线程调度策略。
4.9 终止线程
stop():释放线程已经锁定的资源,可能会导致程序执行的不确定性。
suspend():由于不会释放锁容易发生死锁。
一般建议让线程自行结束。或者设置一个标记位结束(线程处于非运行状态可以触发异常来安全结束线程)
4.10 死锁
必要条件
1. 互斥:资源具有排他性。
2. 请求和保持:线程至少已经持有一个资源并申请新的资源。
3.不剥夺:已获得的资源未释放时不可被剥夺。
4.环路等待。
4.11 守护线程 Daemon
JVM 中只有守护线程在运行时,JVM会自动关闭。
4.12 Join
线程合并:调用该方法的线程执行完 run() 后再执行 join 后的代码。
4.13 线程抛出的异常
线程抛出的异常无法使用 try/catch 捕捉。JDK 5 提供了Thread.UncaughtExceptionHandler 来处理线程中未被捕获的异常。
4.14 线程池
ThreadPoolExecutor 是 ExecutorService 的一个实现。可以最大化利用线程空闲时间和空间。
处理任务的流程:
1. 线程池的线程数量小于 corePoolSize ,创建新线程执行任务。
2. 线程池的数量大于 corePoolSize,暂时把任务存到工作队列等待。
3. 工作队列也满了,线程数小于最大限制,创建新线程执行,若已经超过最大限制,执行拒绝策略。
newFixedThreadPool: 线程池大小固定。
newCachedThreadPool:线程池基本大小为 0,空闲线程会在60s内销毁。每个新任务都会有线程执行。适用于执行速度较快且较小的场景。线程池的大小完全依赖 JVM 能创建的最大线程大小。
newSingleThreadExecutor:单线程的线程池。
newScheduledThreadPool:可以按一定的周期执行任务。
4.15 ThreadLocal
threadLocal.get 获取到的值对每个线程是唯一的。
线程被创建时,线程的对象存储在堆中,栈中存放引用;ThreadLocal 对象被初始化时,存储在堆中的,同时栈中保存引用。当 ThreadLocal 的方法被调用时, JVM会根据引用找到实例,查看 ThreadLocalMap 实例是否被创建并初始化使用。即 ThreadLocal 会把指定值和当前线程绑定在一个 map 里。
4.16 Latch
指定线程等待计数线程完成工作后再执行 latch.await() 之后的代码。CountDownLatch 不可以重用。
4.17 Barier
等待一组线程完成某个条件后再一起执行后续功能的能力。
4.18 Fork / Join
将大任务分割成小的任务后并运行,最后将小任务的最终结果合并为大任务。需保持子任务独立。内部实际为线程池。
4.19 CAS
CAS 保证操作是原子性的,sun.misc.Unsafe 提供一系列相关方法。
ABA问题:更新时增加一个版本号。
4.20 线程调度与优先级
线程的五个状态
1. 新建:创建后的线程进入这个状态。
2. Runnable:start 方法调用后进入可运行状态
3. Running:获得 CPU使用权
4. Blocking:阻塞,放弃CPU;同步阻塞(获取锁失败)会放入锁池。等待阻塞(wait)放入等待队列中。其他(IO、sleep、join)
5. Dead:结束
Java 中优先级可以划分为10个等级。