关键字syncronized与wait()和notify()和notifyAll()可以实现等待通知,类ReentrantLock也可以实现同样的功能,但需要借助于Condition,这个类可以实现多路通知,也就是说在一个Lock对象里可以创建多个Condition实例(对象监视器),线程对象可以注册在指定的Condition中,从而可以选择性的通知线程在线程调度上更加灵活。
而syncronized就相等于整个Lock对象中只有单一的一个Condition对象,所有的线程都注册在它一个上面,线程开始notifyAll()时,需要通知所有的WAITING线程,没有选择权,就会出现资源浪费。下面一个Demo演示的是Condition的错误用法。
public class Demo1 {
private Lock lock = new ReentrantLock ();
private Condition condition = lock.newCondition ();
public void await(){
try {
condition.await ();
} catch (InterruptedException e) {
e.printStackTrace ();
}
}
public static void main(String[] args) {
Demo1 demo1 = new Demo1 ();
new Thread (() -> {
demo1.await ();
}).start ();
}
}
运行后报错:因为在调用Condition.awiat()或single()方法之前需要调用lock.lock()获得同步监视器,切记,跟我们在使用wait方法是同时需要使用synctonized关键字一样一个道理
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.fullyRelease(AbstractQueuedSynchronizer.java:1723)
at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2036)
at Lock.KodyLockTest.ConditionStudy.Demo1.await(Demo1.java:19)
at Lock.KodyLockTest.ConditionStudy.Demo1.lambda$main$0(Demo1.java:28)
at java.lang.Thread.run(Thread.java:748)
双线程通信案例
public class Demo1 {
private Lock lock = new ReentrantLock ();
private Condition condition = lock.newCondition ();
public void await(){
try {
lock.lock ();
System.out.println (Thread.currentThread ().getName () + "线程获得锁");
condition.await ();
} catch (InterruptedException e) {
e.printStackTrace ();
}finally {
System.out.println (Thread.currentThread ().getName () + "线程释放锁");
lock.unlock ();
}
}
public void single(){
lock.lock ();
System.out.println (Thread.currentThread ().getName () + "线程获得锁");
condition.signal ();
System.out.println (Thread.currentThread ().getName () + "线程唤醒睡眠线程");
lock.unlock ();
System.out.println (Thread.currentThread ().getName () + "线程释放锁");
}
public static void main(String[] args) throws InterruptedException{
Demo1 demo1 = new Demo1 ();
new Thread (() -> {
demo1.await ();
}).start ();
TimeUnit.SECONDS.sleep (3);
new Thread (() -> {
demo1.single ();
}).start ();
}
}
控制台信息:首先线程0获得锁后进入等待(意味着放弃了lock对象锁),而后被线程1抢得,线程1接下来唤醒了等待池中等待的线程,此处值得是注册在condition上的线程,目前就只有一个线程0,所以会被唤醒,由于唤醒了线程0但是唤醒了你要继续执行线程0的run代码需要再次获得lock对象锁,这就得看线程1释放没释放了,释放了的话线程0继续抢锁,抢到的话执行完run代码最后也释放。这里需要记住的一点就是线程1唤醒线程0并不能立即执行run代码,因为你需要再次抢锁切记,需要拿到锁才能被唤醒。
Thread-0线程获得锁
Thread-1线程获得锁
Thread-1线程唤醒睡眠线程
Thread-1线程释放锁
Thread-0线程释放锁
双线程交替执行
使用一个Condition对象加一个标识符来实现交替运行
package Lock.KodyLockTest.ConditionStudy;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Heian
* @time 19/10/13 16:13
* @description:线程一打印循环打印 我爱你 线程2打印 love
*/
public class Demo2 {
private static Lock lock = new ReentrantLock ();
private static Condition condition = lock.newCondition ();
//设置一个标识符,如果某个线程打印了则修改标识符位,让自身处于等待,同理另一个线程可以打印
private static Boolean flag = false;//因为用了线程同步,所以该字面修改是线程可见的
public static void main(String[] args) {
new Thread (() -> {
lock.lock ();
for (int i=0;i<100;i++){
try {
while (flag == true){
condition.await ();
}
System.out.println ("我爱你");
TimeUnit.SECONDS.sleep (1);
flag = true;
condition.signal ();
}catch (InterruptedException e){
e.printStackTrace ();
}
}
lock.unlock ();
}).start ();
new Thread (() -> {
lock.lock ();
for (int i=0;i<100;i++){
try {
while (flag == false){
condition.await ();
}
System.out.println ("love");
TimeUnit.SECONDS.sleep (1);
flag = false;
condition.signal ();
}catch (InterruptedException e){
e.printStackTrace ();
}
}
lock.unlock ();
}).start ();
}
}
使用两个Condition对象实现,因为要结束线程声明周期,所以加了个!=10的判断,直接释放锁,而不是还让线程处于等待
package Lock.KodyLockTest.ConditionStudy;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Heian
* @time 19/10/13 16:13
* @description:线程一打印循环打印 我爱你 线程2打印 love
*/
public class Demo3 {
private static Lock lock = new ReentrantLock ();
private static Condition conditionA = lock.newCondition ();
private static Condition conditionB = lock.newCondition ();
private static AtomicInteger atomicIntegerA = new AtomicInteger (0);
private static AtomicInteger atomicIntegerB = new AtomicInteger (0);
public static void main(String[] args) {
new Thread (() -> {
lock.lock ();
for (int i=0;i<10;i++){
try {
TimeUnit.SECONDS.sleep (1);
System.out.println ("我爱你");
conditionB.signal ();//在进入等待池之前先唤醒B,此时A的锁并没有释放
if ( atomicIntegerA.incrementAndGet () != 10){
conditionA.await ();//如果不是最后一个需要你睡眠,是最后一次的话就直接unlock
}
}catch (InterruptedException e){
e.printStackTrace ();
}
}
lock.unlock ();
}).start ();
new Thread (() -> {
lock.lock ();
for (int i=0;i<10;i++){
try {
TimeUnit.SECONDS.sleep (1);
System.out.println ("love");
conditionA.signal ();//在进入等待池之前先唤醒A,此时B的锁并没有释放
if ( atomicIntegerB.incrementAndGet () != 10){
conditionB.await ();
}
}catch (InterruptedException e){
e.printStackTrace ();
}
}
lock.unlock ();
}).start ();
}
}
三个及以上线程交替执行与顺序执行
交替执行案例可以参考我的:多线程面试题整合-CSDN博客 案例7 这里我没做线程结束判断,自己手动加下就行,那顺序执行也是一样的。
这里随机运行三个线程,通过变量num去指定那个线程顺序执行:三个线程随机启动,假设B先启动,B会处于阻塞,假设然后C启动,C也会处于阻塞,当运行到A启动时,会唤醒B,B执行完唤醒C,以此达到ABC顺序运行。
package Lock.KodyLockTest.ConditionStudy;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author Heian
* @time 19/10/13 16:13
* @description: 线程顺序执行
*/
public class Demo4 {
private static Lock lock = new ReentrantLock ();
private static Condition conditionA = lock.newCondition ();
private static Condition conditionB = lock.newCondition ();
private static Condition conditionC = lock.newCondition ();
private static int num = 1;
public static void main(String[] args) {
Thread threadA = new Thread (() -> {
try {
lock.lock ();
while (num != 1) {
conditionA.await ();
}
System.out.println ("我爱你1");
num = 2;//事情做完了,就赋值为2
conditionB.signal ();
} catch (InterruptedException e) {
e.printStackTrace ();
} finally {
lock.unlock ();
}
});
Thread threadB = new Thread (() -> {
try {
lock.lock ();
while (num != 2) {
conditionB.await ();
}
System.out.println ("我爱你2");
num = 3;
conditionC.signal ();
} catch (InterruptedException e) {
e.printStackTrace ();
} finally {
lock.unlock ();
}
});
Thread threadC = new Thread (() -> {
try {
lock.lock ();
while (num != 3) {
conditionC.await ();
}
System.out.println ("我爱你3");
num = 3;
conditionA.signal ();
} catch (InterruptedException e) {
e.printStackTrace ();
} finally {
lock.unlock ();
}
});
threadB.start ();
threadA.start ();
threadC.start ();
}
}