线程

线程状态

暂停状态细分为两种:

  • 阻塞:等待获取一个排它锁。如果其它线程释放了锁就会结束此状态。
  • 等待:又分为无限期等待和限期等待。
    • 无限制等待:必须等待其它线程显示唤醒,否则永远不会分配 CPU 时间片。比如 Object.wati()Thread.join()LockSupport.park()
    • 限期等待:在一定时间后线程被系统自动唤醒。

      阻塞和等待的区别是:阻塞是被动的,它是在获取不到排它锁,因此线程只能阻塞。 而等待是主动的,通过调用 Thread.sleep()Object.wait() 等方法主动让线程进入等待状态。

进入方法 退出方法
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 类开销过大。

    线程中断

    线程中断关注的是线程能否及时响应中断。
    可以中断情况如下:
  1. 处于阻塞状态
  2. 限期等待
  3. 无限期等待状态

但以下情况是不会响应线程中断的:

  1. I/O 阻塞
  2. 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 会确保锁的释放。

线程之间的协作

Java 锁 - 图1