引出
在使用Lock之前,我們使用的最多的同步方式應該是synchronized關鍵字來實現同步方式了。配合Object的wait()、notify()系列方法可以實現等待/通知模式。
Condition接口也提供了類似Object的監視器方法,與Lock配合可以實現等待/通知模式,但是這兩者在使用方式以及功能特性上還是有差別的。
初步使用
利用Condition實現一個簡單的生產者和消費者
public class ConditionTest {
private final static Lock lock = new ReentrantLock(true);
private final static Condition condition = lock.newCondition();
private static int data = 0;
private static volatile boolean noUse = true;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while (true){
buildData();
}
}).start();
new Thread(()->{
while (true){
useData();
}
}).start();
}
/**
* 生產數據
*/
private static void buildData(){
try {
lock.lock(); //synchronized key word #moitor enter
while (noUse){
condition.await(); // monitor.wait()
}
data++;
System.out.println("P:" + data);
Thread.sleep(1000);
noUse = true;
condition.signal(); // monitor.notify()
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // synchronized end #moitor end
}
}
/**
* 消費數據
*/
private static void useData(){
try {
lock.lock(); //synchronized key word #moitor enter
while (!noUse){
condition.await(); // monitor.wait()
}
System.out.println("C:" + data);
Thread.sleep(1000);
noUse = false;
condition.signal(); // monitor.notify()
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); // synchronized end #moitor end
}
}
}
結果
C:0
P:1
C:1
P:2
C:2
......
根據上面的代碼有幾個問題:
- 1.不使用Condition效果相同,是否可以不使用Condition?
- 2.生產者獲得鎖,但是陷入await時,鎖還沒有釋放,生產者怎麼獲得到鎖?
- 3.如果不使用lock只用Condition會怎麼樣?
問題解答
第一個問題
不使用Condition,依然會看起來一樣的原因是:使用了公平鎖,會儘可能保證兩個線程的交替
第二個問題
在接口的上面有上述解釋
The lock associated with this is atomically released and the current thread
In all cases, before this method can return the current thread must re-acquire the lock associated with this condition. When the thread returns it is guaranteed to hold this lock.
可以發現:await和Object.wait()類似,都會自動的釋放鎖,並且在喚起後需要重新獲得鎖
第三個問題
問題的提出是基於第二個問題,既然鎖的獲取沒有意義,是否可以去掉?
如果不加lock,會拋出一個異常,說明:想要await必須需要獲得到鎖
Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.signal(AbstractQueuedSynchronizer.java:1939)
at condition.ConditionTest.useData(ConditionTest.java:66)
at condition.ConditionTest.lambda$main$1(ConditionTest.java:27)
at java.lang.Thread.run(Thread.java:748)
wait和await的區別
等待隊列
public class ConditionTest {
private final static Lock lock = new ReentrantLock();
private final static Condition condition = lock.newCondition();
private static int data = 0;
private static volatile boolean noUse = true;
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 10; i++) {
creatThread();
}
Thread.sleep(3000);
lock.lock();
condition.signalAll();
lock.unlock();
Thread.sleep(1000);
}
public static void creatThread(){
new Thread(() -> {
try {
lock.lock();
System.out.println(Thread.currentThread().getName() + " await.");
condition.await();
System.out.println(Thread.currentThread().getName() + " no await.");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
結果:
Thread-0 await.
Thread-1 await.
Thread-2 await.
Thread-3 await.
Thread-4 await.
Thread-5 await.
Thread-6 await.
Thread-7 await.
Thread-8 await.
Thread-9 await.
Thread-0 no await.
Thread-1 no await.
Thread-2 no await.
Thread-3 no await.
Thread-4 no await.
Thread-5 no await.
Thread-6 no await.
Thread-7 no await.
Thread-8 no await.
Thread-9 no await.
可以發現await的等待隊列是個先進先出的隊列
總結
這是摘自《Java併發編程的藝術》
最大差異:
wait | await |
---|---|
是配合synchronized關鍵字的 | 是配合Lock鎖的 |
等待隊列的喚醒受到JVM的影響,是隨機的喚醒 | 等待隊列FIFO的,先進入先喚醒 |
不可以被打斷 | 可以被打斷 |
等待隊列只有一個 | 每一個Condition都具有一個等待隊列,可以創建多個Condition |
其他地方在使用上並無差異
利用Condition實現生產者和消費者
主要是創建兩個Condition對象:第一個對象是消費者隊列,第二個是生產者隊列。這樣可以實現兩個隊列的分離。
public class ConditionTest {
private final static Lock lock = new ReentrantLock(false);
private final static Condition PRODUCE_CONDITION = lock.newCondition();
private final static Condition CONSUMER_CONDITION = lock.newCondition();
private final static LinkedList<Long> TIMESTAMP_POOL = new LinkedList<>();
private final static int MAX_CAPACITY = 100;
private static int data = 0;
public static void main(String[] args) throws InterruptedException {
IntStream.range(0,6).boxed().forEach(ConditionTest::beginProduce);
IntStream.range(0,13).boxed().forEach(ConditionTest::beginConsume);
}
private static void beginProduce(int i){
new Thread(()->{
while (true){
produce();
sleep(2);
}
},"P-" + i).start();
}
private static void beginConsume(int i){
new Thread(()->{
while (true){
consum();
sleep(2);
}
},"C-" + i).start();
}
private static void produce(){
try {
lock.lock();
while (TIMESTAMP_POOL.size()>=MAX_CAPACITY){
PRODUCE_CONDITION.await();
}
long value = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName() + "P->" + value);
TIMESTAMP_POOL.addLast(value);
CONSUMER_CONDITION.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
private static void consum(){
try {
lock.lock();
while (TIMESTAMP_POOL.isEmpty()){
CONSUMER_CONDITION.await();
}
long value = TIMESTAMP_POOL.removeFirst();
System.out.println(Thread.currentThread().getName() + "C->" + value);
PRODUCE_CONDITION.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
private static void sleep(long sec){
try {
TimeUnit.SECONDS.sleep(sec);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
與 ReentrantLock的關係
protected Collection<Thread> getWaitingThreads(Condition condition)
- 獲取此條件下,阻塞的線程集合
public int getWaitQueueLength(Condition condition)
- 獲取此條件下,阻塞的線程數目
public boolean hasWaiters(Condition condition)
- 獲取此條件是否有線程在阻塞
總結
- 可以發現Condition比較靈活,可以配合ReentrantLock,對阻塞的隊列進行調試
- Condition可以創建多個,可以實現不同角色等待隊列的分離
- Condition在阻塞時是可以被打斷的,方便對線程進行關閉