在多线程应用中,关键在于对状态依赖的管理以及对线程状态的管理。
因为资源不是无限的,如I/O资源、内存资源、CPU资源等,需要对线程进行控制,获取不到资源的线程需要进行阻塞或者挂起,这就引入了队列同步器中的一个关键属性:队列,获取不到资源的线程需要阻塞,然后在队列中等待唤醒。
对于加锁有前验的条件验证,也就是状态依赖。如:一个阻塞队列,put的时候队列不能为满,get的时候队列不能为空等。在一个线程安全类中,通常不止一个需要唤醒线程情况,如果使用Synchronized进行实现,就会产生不少问题,在下面会进行讲解。
如果我们能够将等待不同条件的线程分开进行管理,如一个阻塞队列,在get为空的时候阻塞,则需要被put的操作唤醒,而不是get操作,这时候就引入了条件队列这一概念了,条件队列可以使得一组线程组成一条或多条队列,对特定条件进行排队等待。
条件谓语就是线程能够继续执行的前验条件,一个条件队列可能会有一个或多个条件谓语,条件谓语成真时线程继续执行,否则等待谓语成真。一个条件队列就是一组线程等待一个条件谓语组成的,多个条件队列就组成了一个完整的同步工具类,如 ReentrantLock
由于一个条件队列中可能会有多个条件谓语,在线程被唤醒时,可能并不意味着这个线程等待的条件谓语成真,所以需要在被唤醒之后再进行一次条件谓语的验证,不为真则继续等待。
线程A通知了一个条件队列,线程B随后在这个条件队列中等待,线程B错过了通知需要等待另一个通知才能醒来。在线程等待之前没有做条件谓语为真的校验就会导致丢失信号。
资源状态 State
在独占锁的场景下, 可以理解为资源占有的标识位; 在共享锁的情况下, 可以作为资源数量理解.
双向等待队列(CLH队列)
CLH队列全称是(Craig, Landin, andHagersten) lock queue, 用来存储被阻塞的线程信息.
共享锁
可以有多个线程同时执行, 线程执行个数也会受到state限制, 如Semaphore,CountDownLatch等等
独占锁
当前只有一个线程能够运行, 如ReentrantLock;
1 | //ReentrantLock.java |
1 | //ReentrantLock.java |
AQS在获得资源状态state, 以及添加阻塞线程到CLH队尾时都是使用CAS操作解决线程间资源抢占问题的
1 | compareAndSetState(0, 1); |
1 | //AbstractQueuedSynchronizer.java |
在AQS中, 当线程抢占资源失败时, 会调用unsafe.park()方法阻塞线程;
当其他线程释放资源时, 也会调用unsafe.unpark()唤醒线程;
AQS中将条件谓语封装成 Condition
,一个 Condition
会记录线程等待头节点和尾节点,实现在唤醒时对需要的阻塞线程进行唤醒
1 | //AbstractQueuedSynchronizer.java |