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()
方法时,相当于同步队列中的节点移动到等待队列中。
- 关于signal()方法:
- 其他线程调用调用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(); } } // 条件满足被,当前线程被唤醒,打印shared的值 System.out.println("print线程:shared的值为" + waitUse.shared); } } }).start(); new Thread(new Runnable() { @Override public void run() { synchronized (waitUse) { // 让shared增加至100 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(); } // 条件满足被,当前线程被唤醒,打印shared的值 System.out.println("print线程:shared的值为" + shared); } public synchronized void add() { // 让shared增加至100 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() 和 sleep() 的区别:
- 所属的对象:
wait()
方法是Object的普通方法,sleep()
方法是Thread的静态方法。 - 是否会释放锁:
wait()
方法会释放锁,sleep()
方法 不会。 - 使用限制:
wait()
方法必须在同步方法或同步代码块中使用,使用前必须获取对象的锁;sleep()
方法可以在任何地方使用,让当前线程休眠一段时间。二者都需要处理InterruptedException
异常。 - 使用场景:
wait()
方法一般与notify()
方构成等待/通知模式,用于进行线程间的通信;sleep()
方法一般用于让当前线程休眠一段时间。 - 是否发生上下文切换:
wait()
方法不一定导致上下文切换,sleep()
方法强制CPU进行上下文切换。
③ 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() {
// 添加第5个元素
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();
}