3.1 线程

3.1.1 生命周期和状态

线程的上下文切换:
  • 主动让出CPU:调用sleep、wait函数
  • 时间片用完
  • 调用了阻塞类型的系统中断
  • 被终止或者结束运行
Thread#sleep()方法和Object#wait()方法对比:
  • 两者都可以暂停线程的运行
  • sleep方法没有释放锁,而wait方法释放了锁
  • wait用于线程之间交互通信,而sleep主要用于暂停执行
  • wait方法执行后,线程不会主动苏醒,需要别的线程调用同一个对象上的notify或者notifyAll方法。sleep方法会主动苏醒,或者使用wait(long timeout)超时后也会主动苏醒。
  • sleep()是Thread类的静态本地方法,而wait是Object类的本地方法。
为什么wait方法不定义在Thread中?

wait方法是为了让获得对象锁的线程实现等待,会自动释放当前线程占有的对象锁,每个对象都拥有对象锁,既然要释放当前线程占有的对象锁并让其进入等待状态,自然应该操作对应的对象,而不是当前线程。

3.2 Volatile和Synchronized关键字

3.2.1 volatile关键字

volatile关键字可以保证变量的可见性,指示JVM这个共享变量是不稳定的,每次使用时都需要去主存中进行读取,但是volatile关键字不保证数据的原子性。

volatile关键字还可以防止JVM 的指令重拍,对修饰的变量进行读写时,会插入特定的内存屏障禁止指令重排序。

3.2.2 synchronized关键字

synchronized关键字早期属于重量级锁,因为其底层monitor(监视器锁)基于操作系统的Mutex Lock实现,需要进行用户态和内核态的切换,Java 6之后对synchronized引入了大量的优化来减少锁操作的开销。

synchronized可以对对象加锁,修饰静态方法,修饰代码块。构造方法不能使用synchronized关键字,因为构造方法本身是线程安全的,但是如果构造方法中使用了共享资源,可以在构造方法内使用synchronized关键字修饰代码块。

3.2.3 synchronized和violatile的区别

  • volatile关键字是线程同步的轻量级实现,volatile关键字只能用于变量,而synchronized关键字可以修饰方法和代码块。
  • volatile关键字能保证数据的可见性,但是无法保证数据的原子性,synchronized两者都可以保证。
  • 【可见性】避免读取变量在缓存中的旧值,而是强制读取主存中的新值。

3.3 乐观锁和悲观锁

3.3.1 悲观锁

假设最坏情况,访问共享资源时,先获取锁。

synchronized和ReentrantLock等独占锁都是悲观锁思想的实现。

3.3.2 乐观锁

假设最好情况,访问访问共享资源时不需要获取锁,提交修改时验证资源是否发生了冲突。

可以通过CAS机制或版本号机制实现乐观锁。

CAS机制依赖于CPU的原子指令,但是无法避免ABA问题。

3.3.3 ReentrantLock

ReentrantLock实现了Lock接口,有一个内部类Sync,Sync继承AQS(AbstractQueuedSynchronizer),而Sync有公平锁和非公平锁两个子类。ReentrantLock默认使用非公平锁。

  • 公平锁保证按照申请顺序获取锁。
  • 非公平锁不保证按照申请顺序获取锁,可能会出现饿死的情况。

3.3.4 synchronized和ReentrantLock有什么区别?

  • 两者都是可重入锁
  • synchronized依赖于JVM,而ReentrantLock依赖于API
  • ReentrantLock比synchronized增加了一些高级功能:等待可中断、可实现公平锁,可实现选择性通知。

写锁可以降级为读锁,但是读锁不能升级为写锁,为了避免死锁的情况。

3.4 TreadLocal

3.5 线程池

3.6 Future

3.7 AQS