线程
线程状态
暂停状态细分为两种:
- 阻塞:等待获取一个排它锁。如果其它线程释放了锁就会结束此状态。
- 等待:又分为无限期等待和限期等待。
- 无限制等待:必须等待其它线程显示唤醒,否则永远不会分配 CPU 时间片。比如
Object.wati()
、Thread.join()
、LockSupport.park()
- 限期等待:在一定时间后线程被系统自动唤醒。
阻塞和等待的区别是:阻塞是被动的,它是在获取不到排它锁,因此线程只能阻塞。 而等待是主动的,通过调用
Thread.sleep()
和Object.wait()
等方法主动让线程进入等待状态。
- 无限制等待:必须等待其它线程显示唤醒,否则永远不会分配 CPU 时间片。比如
进入方法 | 退出方法 |
---|---|
Thread.sleep() 方法 |
时间结束 |
设置了 Timeout 参数的 Object.wait() 方法 | 时间结束 / Object.notify() / Object.notifyAll() |
设置了 Timeout 参数的 Thread.join() 方法 | 时间结束 / 被调用的线程执行完毕 |
LockSupport.parkNanos() 方法 | - |
LockSupport.parkUntil() 方法 | - |
线程的使用方式
共有三种:
线程使用方式 | 特点 |
---|---|
实现 Runnable 接口 | 无返回值 |
实现 Callable 接口 | 有返回值,通过 FutureTask 对象封装 |
继承 Thread 类 | 重写 run() 方法 |
接口 VS 继承:
- Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口;
- 类可能只要求可执行就行,继承整个 Thread 类开销过大。
线程中断
线程中断关注的是线程能否及时响应中断。
可以中断情况如下:
- 处于阻塞状态
- 限期等待
- 无限期等待状态
但以下情况是不会响应线程中断的:
- I/O 阻塞
- synchronized 锁阻塞
interrupted()
方法会设置线程的中断标记,可以在循环中使用 interrupted()
方法来判断线程是否处于中断状态,从而结束线程。
Executor 中断操作
shutdown()
会等待线程全都执行完毕才会关闭,调用 shutdownNow()
则相当于调用每个线程的 interrupt()
方法。
ReentrantLock 和 Synchronized 比较
1. 锁的实现
synchronized 是 JVM 实现的,而 ReentrantLock 是 JDK 实现的。
2. 性能
新版本 Java 对 synchronized 进行了很多优化,例如自旋锁等,synchronized 与 ReentrantLock 大致相同。
3. 等待可中断
当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,改为处理其他事情。
ReentrantLock 可中断,而 synchronized 不行。
4. 公平锁
公平锁是指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。
synchronized 中的锁是非公平的,ReentrantLock 默认情况下也是非公平的,但是也可以是公平的。
5. 锁绑定多个条件
一个 ReentrantLock 可以同时绑定多个 Condition 对象。
使用选择
除非需要使用 ReentrantLock 的高级功能,否则优先使用 synchronized。这是因为 synchronized 是 JVM 实现的一种锁机制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。并且使用 synchronized 不用担心没有释放锁而导致死锁问题,因为 JVM 会确保锁的释放。