juc并发编程03——Lock与Condition接口

Java
313
0
0
2022-12-05
标签   Java并发

Lock与Condition接口

前面两篇文章回顾了传统的synchronized关键字、JMM内存模型、volitile关键字,这篇文章开始我们正式介绍juc包。

在jdk5之后,juc提供了Lock,与synchronized相似,都可以实现锁功能,但是需要手动获取锁和释放锁。不过它与synchronized又不太一样,synchronized关键字相当于是使用的其它类的monitor关联,在mark word中记录锁的信息。而Lock则可以认为是一个锁对象。

看看Lock源码的方法。

public interface Lock {
//获取锁
void lock();
// 获取锁,等待过程中响应中断
void lockInterruptibly() throws InterruptedException;
// 尝试获取锁,不会阻塞,成功则返回true,否则返回false
boolean tryLock();
// 尝试获取锁,如果获取不到则等待限定时间,超时未获取锁则返回false,可以响应中断
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//释放锁
void unlock();
// 可以理解为wait、notify的替代方案
Condition newCondition();
}

简单应用下。

public class Demo8 {
private static int i = 0;
public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock();
Runnable action = () -> {
for (int j = 0; j < 100000; j++) {
lock.lock();
i ++;
lock.unlock();
}
};
new Thread(action).start();
new Thread(action).start();
Thread.sleep(1000);
System.out.println(i);
}
}

可以看到,使用Lock,是真正的在操作一个锁对象。

使用synchronized可以进行waitnotify

synchronized (Demo8.class) {
Demo8.class.wait();
}

使用Lock配合condition接口可以实现类似功能。而且,使用synchronized关键字只能使用其加锁对象的waitnotify方法,只能有一个等待队列,就是加锁对象(如Demo8.class)的等待队列。而使用LockCondition则可以有多个等待队列。

来一起阅读下Condition类的源码。

public interface Condition {
// 对应wait()方法,需要signal()或者signalAll()唤醒,可以响应中断
void await() throws InterruptedException;
// 等待,不响应中断
void awaitUninterruptibly();
// 等待指定时间(纳秒),如果未超时被唤醒则返回剩余时间,超时返回false,可以响应中断
long awaitNanos(long nanosTimeout) throws InterruptedException;
// 等待指定时间,如果未超时被唤醒则返回true,超时返回0或者负数,可以响应中断
boolean await(long time, TimeUnit unit) throws InterruptedException;
// 指定明确的时间点,在该时间点前被唤醒则返回true,否则false,可以响应中断
boolean awaitUntil(Date deadline) throws InterruptedException;
// 唤醒任一线程,该线程进入就绪状态
void signal();
// 唤醒所有线程
void signalAll();
}

下面实战使用下。

public class Demo9 {
public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
new Thread(() -> {
lock.lock();
System.out.println("await....");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("await finished");
lock.unlock();
}).start();
Thread.sleep(100);
new Thread(() -> {
lock.lock();
System.out.println("signal...");
condition.signal();
System.out.println("thread 2 finished");
lock.unlock();
}).start();
}
}

输出的结果如下。

await....
signal...
thread 2 finished
await finished

值的关注的一点是,不管使用await还是signal都要在获取锁的前提下。这与waitnotify实际上是一致的。不过,在使用了await以后,锁就被释放了,否则主线程也不可能继续执行。

sychronized关键字不同的是,同一把锁可以创建多个Condition,每个Condition对象都拥有独立的等待队列。看如下代码。

public class Demo9 {
public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
Condition condition1 = lock.newCondition();
new Thread(() -> {
lock.lock();
System.out.println("await....");
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("await finished");
lock.unlock();
}).start();
Thread.sleep(100);
new Thread(() -> {
lock.lock();
System.out.println("signal...");
condition1.signal();
System.out.println("thread 2 finished");
lock.unlock();
}).start();
}
}

其输出结果如下。

await....
signal...
thread 2 finished

显然,condition1signal没能唤醒conditionawait的线程。

当然,也并不是用了await就需要signal,因为可以限定时长。

public class Demo10 {
public static void main(String[] args) throws InterruptedException {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
lock.lock();
try {
System.out.println(condition.await(3, TimeUnit.SECONDS));
} finally {
lock.unlock();
}
}
}

打印结果为false,因为超过3s也没有被唤醒。