java.util.concurrent.locks包中提供了几个接口:Lock、Condition......
一、java.util.concurrent.locks.Lock
1、interface Lock:Lock实现提供了比使用synchronized方法(同步函数)和语句(同步代码块)可获得的更广泛的锁定操作
2、JDK1.5以后,将同步和锁封装成了对象,并将操作锁的隐式方法定义到了该对象中,将隐式动作变成了显式动作
注:Lock出现的目的:替代synchronized,包括:同步代码块和同步函数。因为同步代码块和同步函数只是一个封装体,封装体中自带锁(对锁的操作是隐式的:获取和释放锁都是底层自动完成的)。而Lock是一个锁对象,具备着操作锁的方法(对锁的操作是显式的:获取和释放锁都是通过调用方法完成的)
3、Lock接口的实现允许锁在不同的作用范围内获取和释放,并允许以任何顺序获取和释放多个锁
(synchronized方法或语句的使用提供了对与每个对象相关的隐式监视器锁的访问,但却强制所有锁获取和释放均要出现在一个块结构中:当获取了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的词法范围内释放所有锁)
4、锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try/finally 或 try/catch 加以保护,以确保在必要时释放锁
(使用结构块锁,就具备使用synchronized方法和语句时会出现的锁自动释放功能)
5、方法
(1)void lock():获取锁。如果锁不可用,出于线程调度目的,将禁用当前线程,并且在获得锁之前,该线程将一直处于休眠状态
(2)void unlock():释放锁。释放锁的动作一定要执行,所以,unlock()方法应该放在finally代码块中
(3)Condition newCondition():返回绑定到此Lock实例的新Condition实例。在等待条件前,锁必须由当前线程保持。调用Condition.await()将在等待前以原子方式释放锁,并在等待返回前重新获取锁
注:Condition实例的具体操作依赖于Lock实现,并且该实现必须对此加以记录
(4)boolean tryLock():仅在调用时锁为空闲状态才获取该锁
(5)boolean tryLock(long time, TimeUnit unit) throws InterruptedException:如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁
(6)void lockInterruptibly() throws InterruptedException:如果当前线程未被中断,则获取锁
6、代码演变
//最开始的同步代码。Object obj是锁
Object obj = new Object();
public void show() {
synchronized (obj) {
//code...
}
}
//JDK1.5后,将同步和锁封装成了锁对象Lock
//ReentrantLock:一个可重入的互斥锁Lock,是Lock接口的子类
Lock lock = new ReentrantLock();
//用Lock替代synchronized,所以,不用synchronized做同步了
public void show() {
//获取锁
lock.lock();
//code...
//释放锁
lock.unlock();
}
问题:获取锁后,在执行code代码时发生异常,程序跳转,释放锁的代码执行不到,导致其他线程都获取不到锁
解决方法:释放锁的动作一定要执行。将释放锁的 lock.unlock() 方法放在finally代码块中
//JDK1.5后,将同步和锁封装成了锁对象Lock
//ReentrantLock:一个可重入的互斥锁Lock,是Lock接口的子类
Lock lock = new ReentrantLock();
//用Lock替代synchronized,所以,不用synchronized做同步了
public void show() {
//获取锁
lock.lock();
try {
//将可能会发生异常的代码放在try代码块中
//code...
} finally {
//异常是否被处理catch不一定
//但一定要将锁释放,写在finally中
//释放锁
lock.unlock();
}
}
7、用Lock改写多生产者多消费者问题的代码
/**
* 此段代码有问题
*/
//导包
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Resource {
private String name;
private int count = 1;
private boolean flag = false;
//创建一个锁对象
Lock lock = new ReentrantLock();
//用Lock替代synchronized,所以,不用synchronized做同步了
// public synchronized void set(String name) {
public void set(String name) {
//获取锁
lock.lock();
try {
//使用while替换if
while (flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName() + "...生产者..." + this.name);
this.flag = true;
//使用notifyAll()全唤醒
this.notifyAll();
} finally {
//释放锁
lock.unlock();
}
}
//用Lock替代synchronized,所以,不用synchronized做同步了
// public synchronized void out() {
public void out() {
//获取锁
lock.lock();
try {
//使用while替换if
while (!flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "......消费者......" + this.name);
this.flag = false;
//使用notifyAll()全唤醒
this.notifyAll();
} finally {
//释放锁
lock.unlock();
}
}
}
二、java.util.concurrent.locks.Condition
1、之前同步中的锁是自定义的this、obj等,锁操作锁上的线程this.wait()等。现在的锁是自己创建的,没有this锁,不能使用this.wait()了,要使用 new Lock() 锁上的方法
2、interface Condition:将Object监视器方法(wait()、notify()、notifyAll())分解成截然不同的对象,以便通过将这些对象与任意Lock实现组合使用,为每个对象提供多个等待set(wait-set)。其中,Lock替代了synchronized方法和语句的使用,Condition替代了Object监视器方法的使用
3、之前的同步代码,一个锁上只能有一组监视器方法wait()、notify()、notifyAll()。因为每个锁都是Object的子类,只能拿到一组方法。而Condition将Object中的方法进行了单独的封装,封装成一个Condition对象。一个Lock锁上可以挂多个Condition,每个Condition都有一组监视器
(可以将监视器方法封装成Condition对象,一个Condition对象中封装一组监视器方法。有多个Condition对象,它们都可以属于同一个锁Lock,这些Condition的方法都可以作用到Lock锁的线程上)
4、获取Condition
(1)Condition newCondition():返回绑定到此Lock实例的新Condition实例(返回新的监视器方法实例)
注:Condition实例实质上被绑定到一个锁上(因为监视器必须得监视锁上的线程)。要为特定Lock实例获得Condition实例,请使用其newCondition()方法
//创建一个锁对象
Lock lock = new ReentrantLock();
//通过已有的锁,获取该锁上的监视器对象
//一个Lock锁上可以有多个Condition监视器对象
Condition con1 = lock.newCondition();
Condition con2 = lock.newCondition();
5、方法
(1)void await() throws InterruptedException:造成当前线程在接到信号或被中断之前一直处于等待状态
(2)boolean await(long time, TimeUnit unit) throws InterruptedException:造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。此方法在行为上等效于:awaitNanos(unit.toNanos(time)) > 0
(3)long awaitNanos(long nanosTimeout) throws InterruptedException:造成当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态
(4)void awaitUninterruptibly():造成当前线程在接到信号之前一直处于等待状态
(5)boolean awaitUntil(Date deadline) throws InterruptedException:造成当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态
(6)void signal():唤醒一个等待线程。如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从await()返回之前,该线程必须重新获取锁
(7)void signalAll():唤醒所有等待线程。如果所有的线程都在等待此条件,则唤醒所有线程。在从await()返回之前,每个线程都必须重新获取锁
注:Object中的监视器方法和Condition中的监视器方法之间的对应关系
(1)wait() --> await()
(2)notify() --> signal()
(3)notifyAll() --> signalAll()
6、一个Lock锁上可以有多组Condition监视器(监视器所属于锁),每个Condition监视器上都有await()、signal()、signalAll()方法。所以,一个Lock锁上可以有多组Condition监视器方法
三、使用JDK1.5新特性解决多生产者多消费者问题
1、以前一个锁上只有一组监视器,这组监视器既监视着生产者,又监视着消费者。意味着这组监视器能将生产者和消费者全都wait()或唤醒(notify()、notifyAll())。而现在线程有了分类,一组负责生产,一组负责消费。希望生产者/消费者可以唤醒消费者/生产者。使用两个监视器,一组监视生产者,一组监视消费者(四个线程共用同一把锁,但是监视器不同)
2、锁的替换
(1)Lock <-- 同步synchronized
(2)Condition <-- Object中的监视器方法
3、监视器方法替换
(1)await() <-- wait()
(2)signal() <-- notifyAll()
注:生产者线程和消费者线程共用一个锁Lock,但可以根据Lock锁获取两组Condition监视器对象,一组监视生产者线程,一组监视消费者线程。这样,await()和signal()时,可以分别指定操作哪组线程。使用signal()替代notifyAll(),而不用signalAll()
//导包
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class Resource {
private String name;
private int count = 1;
private boolean flag = false;
//创建一个锁对象
//ReentrantLock:是Lock接口的一个子类
Lock lock = new ReentrantLock();
//通过已有的锁,获取该锁上的两组监视器对象。一组监视生产者,一组监视消费者
//一个Lock锁上可以有多个Condition监视器对象
Condition producer_condition = lock.newCondition();
Condition consumer_condition = lock.newCondition();
//有Lock就不使用synchronized
public void set(String name) {
//获取锁
lock.lock();
try {
//使用while替换if
while (flag) {
try {
//生产者等待
producer_condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName() + "...生产者 5.0..." + this.name);
this.flag = true;
//唤醒消费者
//因为唤醒的是消费者线程中的内容,所以,使用signal()唤醒任意一个即可
//不用全唤醒,全唤醒也只能有一个执行
consumer_condition.signal();
} finally {
//释放锁
lock.unlock();
}
}
//有Lock就不使用synchronized
public void out() {
//获取锁
lock.lock();
try {
//使用while替换if
while (!flag) {
try {
//消费者等待
consumer_condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "......消费者 5.0......" + this.name);
this.flag = false;
//唤醒生产者
//因为唤醒的是生产者线程中的内容,所以,使用signal()唤醒任意一个即可
//不用全唤醒,全唤醒也只能有一个执行
producer_condition.signal();
} finally {
//释放锁
lock.unlock();
}
}
}
class Producer implements Runnable {
private Resource r;
Producer(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
r.set("烤鸭");
}
}
}
class Consumer implements Runnable {
private Resource r;
Consumer(Resource r) {
this.r = r;
}
@Override
public void run() {
while (true) {
r.out();
}
}
}
public class Test {
public static void main(String[] args) {
Resource r = new Resource();
Producer producer = new Producer(r);
Consumer consumer = new Consumer(r);
Thread t0 = new Thread(producer);
Thread t1 = new Thread(producer);
Thread t2 = new Thread(consumer);
Thread t3 = new Thread(consumer);
t0.start();
t1.start();
t2.start();
t3.start();
}
}
四、总结
1、Lock接口:Lock接口的出现替代了同步代码块或同步函数,将同步的隐式锁操作变成了显式锁操作。同时更为灵活,可以一个锁上加多组监视器
(1)lock():获取锁
(2)unlock():释放锁。通常需要定义在finally代码块中
2、Condition接口:Condition接口的出现替代了Object中的wait()、notify()、notifyAll()方法,将这些监视器方法单独进行了封装,变成了Condition监视器对象,可以和任意的锁进行组合
(1)await():wait()
(2)signal():notify()
(3)signalAll():notifyAll()
五、范例
1、Lock
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
2、Condition -- 多生产者多消费者问题实际开发代码
class BoundedBuffer {
//锁
final Lock lock = new ReentrantLock();
//监视器
//非满
final Condition notFull = lock.newCondition();
//非空
final Condition notEmpty = lock.newCondition();
//数组容器,存的是对象
final Object[] items = new Object[100];
//用来操作数组的变量:存,取,计数器
//有数组就得有指针,存和取的指针得分别定义putptr、takeptr,同时还得记录数组中元素的个数
int putptr, takeptr, count;
//await()方法抛出异常,因为方法内没有做catch处理,想让调用者处理。也可以在方法内处理
public void put(Object x) throws InterruptedException {
//获取锁。此时,take()不能取,因为用的是同一个锁,互斥
lock.lock();
try {
//判断标记时一定要用while。因为每次醒来都先判断标记,安全
//while是必须的
while (count == items.length) {
//存满了,生产者等待
notFull.await();
}
//生产一个存一个
items[putptr] = x;
if (++putptr == items.length) {
//生产到最后一个后,继续从0开始存
putptr = 0;
}
++count;
//signal()、signalAll()用哪个不一定。如果signal()能实现唤醒对方,就不需要signalAll()
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
//判断标记时一定要用while。因为每次醒来都先判断标记,安全
//while是必须的
while (count == 0) {
//取完了,消费者等待
notEmpty.await();
}
//从默认的0角标开始取
Object x = items[takeptr];
if (++takeptr == items.length) {
//取到最后一个后,继续从0开始取
takeptr = 0;
}
--count;
//signal()、signalAll()用哪个不一定。如果signal()能实现唤醒对方,就不需要signalAll()
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}