1. LockSupport工具类
- LockSupport工具类提供了最基本的线程阻塞和唤醒功能,是构建同步组件的基础工具。
LockSupport
工具类定义了一组以park开头的阻塞方法,可以通过unpark()
方法进行唤醒。
- void park(): 阻塞当前线程,使线程从Runnable状态转到Waiting状态。直到调用
unpark()
方法或响应中断,才能从park()
方法返回
- void parkNanos(long nanos) : 阻塞当前线程一段时间,单位是纳秒,使线程从Runnable状态进入timed-Waiting状态。直到阻塞时间结束,或者调用
unpark()
,或者响应中断,才能从parkNanos(naos)
方法返回.
- void parkUntil(long deadline): 与
parkNanos(naos)
方法不同的是,deadline指的是从1970年到deadline的毫秒数。
- void unpark(Thread thread): 唤醒处于阻塞状态的线程,使线程从Waiting状态或者timed-Waiting状态回到Runnable状态。
- 为了弥补Java 5遗漏了线程的阻塞对象这一问题,Java 6中的LockSupport工具类,提供了带阻塞对象的阻塞方法。
- 包括
void park(Object blocker)
、void parkNanos(Object blocker, long nanos)
、void parkUntil(Object blocker, long deadline)
,参数blocker表示当前线程在等待的对象,简称阻塞对象。
- 通过在阻塞方法中携带阻塞对象,可以为程序员提供更多的现场信息,方便问题的定位。
2. Condition接口
① 对象的监视器方法与Condition接口
- 在synchronized实现同步时,每个Java对象都有monitor对象与之绑定,通过进入和退出Monitor对象实现方法同步或者代码块同步。
- 每个Java对象都拥有一组监视器方法:
wait()
、wait(timeout)
、notify()
、notifyAll()
,这些监视器方法与synchronized关键字配合,实现等待/通知模式。
- Condition接口也提供了类似
Object
的监视器方法,与Lock配合实现等待/通知模式。
② Condition接口与示例
- Condition对象由Lock对象的
newCondition()
方法创建,即Condition对象与Lock对象是绑定的,而且一个Lock对象可以绑定多个Condition对象。
- Condition对象的一般使用方法:
- 一般会将Condition对象作为成员变量,调用Condition对象的
await()
方法,当前线程会释放锁并进入等待状态
- 直到其他线程调用Condition对象的
singal()
方法或者singalAll()
方法,当前线程才会从await()
方法返回,并且在返回前一定获取了Condition对象关联的锁。
- Condition接口的常用API:
await()
、await(long time, TimeUnit unit)
、awaitNanos(long nanosTimeout)
、 awaitUntil(Date deadline)
、signal()
、signalAll()
。
- 通过Condition创建一个有界队列(没有过多的考虑,只是实现了先进后出):
public class BoundedQueue {
private Lock lock;
private Condition notFull;
private int count;
private Condition notEmpty;
private Object[] items;
public BoundedQueue(int size) {
lock = new ReentrantLock();
notEmpty = lock.newCondition();
notFull = lock.newCondition();
items = new Object[size];
count = 0;
}
public void add(Object t) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
notFull.await();
}
items[count] = t;
count++;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object remove() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await();
}
Object t = items[--count];
notFull.signal();
return t;
} finally {
lock.unlock();
}
}
}
- 创建固定大小的队列,初始化Lock对象,创建两个Condition对象:
notFull
用于实现队列满/非满的等待/通知模式。notEmpty
用于实现队列空/非空的等待/通知模式。
- 添加元素时,如果发现队列已满,则调用
notFull.await()
,让当前线程等待队列变得非满。直到退出while循环,然后可以向队列中添加元素。
- 向队列中添加元素后,调用
notEmpty.signal()
,告诉等待从队列中获取元素的线程,现在可以获取元素了。
- 获取元素是,如果发现队列为空,则调用
notEmpty.await()
,让当前线程等待队列非空。直到退出while循环,然后可以从队列中移除元素。
- 从队列中移除元素后,调用
notFull.signal()
,告诉等待向队列添加元素的的线程,现在可以添加元素了。
② Condition对象的等待队列
- Condition对象中有一个等待队列,它是一个由链表组成的FIFO单向队列。有两个引用:
firstWaiter
和lastWaiter
,分别用于指向等待队列中的头节点和尾节点。

- 等待队列的节点添加和移除:
- 如果一个线程调用了condition对象的
await()
方法,则该线程会释放锁,构成节点加入到等待队列中,并进入等待状态。
- 如果其他线程调用了该condition对象的
signal()
方法,则会唤醒在等待队列中等待最久的节点(队列的头节点)。
- 同步器中,
ConditionObject
是其内部类,同步器不仅拥有自己的同步队列,还有ConditionObject
的等待队列。
- 注意: Java对象的监视器模型中,一个Java对象只有一个同步队列和一个等待队列;而AQS中可以拥有一个同步队列和多个等待队列。因为,AQS可以绑定多个Condition对象。

- 关于await()方法:
- 调用Condition对象的
await()
方法,要求当前线程已经获取到锁,即当前线程处于同步队列中的头节点。
- 当前线程调用Condition对象的
await()
方法后,会释放锁并把自己构造成新的节点,通过addConditionWaiter()
方法加入到等待队列中。
- 从队列的角度看,调用
await()
方法时,相当于同步队列中的节点移动到等待队列中。

调用await()方法,当前线程进入等待队列
- 其他线程调用调用Condition对象的
signal()
方法,会唤醒等待队列中等待时间最长的节点(其实并未醒来),即等待队列中的头节点。
- 位于头节点的中的当前线程会从
await()
方法中返回,在返回之前它必须获取到了condition对象关联的锁。只有获取到锁,当前线程才会被唤醒(真正醒来)。
- 因此,当前线程又会被构造新的步节点并加入同步队列中,等待锁的获取。
- 从队列中的角度看,调用
signal()
方法时,节点从等待队列移动到同步队列。

调用singal()方法,节点从等待队列移动到同步队列
- 关于singalAll()方法:等待队列中的所有节点都会执行一次
singal()
方法,即等待队列中的所有节点都会移动到同步队列中,并唤醒每个节点的线程。
3. 几种阻塞和唤醒方法的比较
① Thread.join()方法
- 在A线程汇总调用
B.join()
方法,会将A线程挂起,直到B线程运行结束。
- 感觉就像排队缴费时,甘愿让某人先缴费,当直到他缴费完毕,自己再继续缴费。
- 在线程中调用另一个线程的
join()
方法,会将当前线程挂起,而不是忙等待,直到目标线程结束。
- 编程实例:
public class JoinUse {
public static void main(String[] main) {
Thread myThread = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("myThread: 计数值为" + i);
}
}
});
myThread.start();
try {
myThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("主线程:myThread运行结束");
}
}

② wait()和notify()/notifyAll()
wait()
、notify()
、notifyAll()
,属于Java对象自带的监视器方法,不属于Thread类。
- 与
synchronized
关键字一起使用,构成等待/通知模式。
- 当前线程调用wait() 方法使自己挂起,等待某个条件的满足。其他线程的运行使得条件满足是,会调用调用
notify()
或者 notifyAll()
来唤醒挂起的线程。
- 编程实例1:代码块同步。包含
wait()
和notify()
方法的两个线程中,同步对象应该是同一个对象,否则会报错:java.lang.IllegalMonitorStateException
。
public class WaitUse {
private int shared = 0;
public static void main(String[] args) {
WaitUse waitUse = new WaitUse();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (waitUse) {
while (waitUse.shared != 100) {
try {
waitUse.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("print线程:shared的值为" + waitUse.shared);
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (waitUse) {
while (waitUse.shared != 100) {
waitUse.shared++;
}
System.out.println("add线程:shared值为100,可以打印了");
waitUse.notify();
}
}
}).start();
}
}
- 编程实例2:方法同步。在同一个类中定义两种同步方法,一个调用
wait()
方法,一个调用调用notify()
方法。在主线程中创建该类的对象,然后定义不同的线程分别调用对象的同步方法。
public class WaitUse {
private int shared = 0;
public synchronized void print() {
try {
while (shared != 100) {
wait();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("print线程:shared的值为" + shared);
}
public synchronized void add() {
while (shared != 100) {
shared++;
}
System.out.println("add线程:shared值为100,可以打印了");
notify();
}
public static void main(String[] args) {
WaitUse waitUse = new WaitUse();
new Thread(new Runnable() {
@Override
public void run() {
waitUse.print();
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
waitUse.add();
}
}).start();
}
}
-
- 所属的对象:
wait()
方法是Object的普通方法,sleep()
方法是Thread的静态方法。
- 是否会释放锁:
wait()
方法会释放锁,sleep()
方法 不会。
- 使用限制:
wait()
方法必须在同步方法或同步代码块中使用,使用前必须获取对象的锁;sleep()
方法可以在任何地方使用,让当前线程休眠一段时间。二者都需要处理InterruptedException
异常。
- 使用场景:
wait()
方法一般与notify()
方构成等待/通知模式,用于进行线程间的通信;sleep()
方法一般用于让当前线程休眠一段时间。
- 是否发生上下文切换:
wait()
方法不一定导致上下文切换,sleep()
方法强制CPU进行上下文切换。
sleep()
③ await()和signal()/signalAll()方法
await()
、signal()
、signalAll()
是JUC包中Condition
接口提供与Object类似的监视器方法,它必须由Lock对象newCondition()
方法创建,因此,必须与Lock配合使用,构成等待/通知模式。
- 当前线程调用
await()
方法,当前线程挂起,等待被其他线程唤醒;其他线程继续运行在条件满足时,调用signal()
唤醒被挂起的线程。
await()
方法可以指定当代条件,比wait()
方法更灵活。
- 编程实例见上文,使用condition实现有界队列。
public static void main(String[] arg) {
BoundedQueue queue = new BoundedQueue(4);
for (int i = 1; i <= 4; i++) {
try {
System.out.println("添加元素" + i);
queue.add("元素" + i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("添加元素5");
queue.add("元素5");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 1; i <= 5; i++) {
try {
System.out.println("获取" + queue.remove());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}).start();
}