实现同步,我们可以使用 synchronized ,也可以使用 Java SE 5 之后新增的 Lock 接口。
synchronized 和 Lock 的区别
1)synchronized 是 Java 中的关键字;Lock 是个接口
2)synchronized 在发生异常时,会自动释放锁;Lock 必须手动释放锁,应该使用 try…finally ,避免死锁
3)Lock 等待锁的过程中可以使用 interrupt 中断等待;synchronized 不能响应中断
4)Lock 可以判断当前线程是否获取到锁;synchronized 不能
5)Lock 有多个子类,实现了多样化的锁
Lock API
方法名称 | 描述 | 模板 |
---|---|---|
void lock() | 获取锁 | lock.lock();try {} finally {lock.unlock();} |
void lockInterruptibly() throws InterruptedException | 调用lockInterruptibly获取锁的线程,在等待锁和在获取锁但还没获取到锁的一瞬间可以被其他线程中断。如果没有被其他线程中断,并且锁是空闲的,则成功获取到锁,获取到锁之后,即使其他线程调用了该线程的中断,也不会使线程中断 | try { lock.lockInterruptibly();try{}finally{lock.unlock();}} catch (InterruptedException e) {e.printStackTrace();} |
boolean tryLock() | 返回 true表示已获取到锁,返回 false表示不能获取到锁,此方法不会阻塞执行线程 | if (lock.tryLock()){try {} finally {lock.unlock();}}else {} |
boolean tyrLock(long time,TimeUnit unit) throws InterruptedException | 在超时时间内尝试获取锁,返回true表示已获取到锁,返回false表示不能获取到锁 | 同tryLock() |
void unlock | 释放锁 | 没有获取锁而直接调用unlock会出现java.lang.IllegalMonitorStateException异常,针对上面获取锁的方式,一定要注意unlock的位置 |
Condition newCondition | 获取等待通知组件,该组件与当前线程绑定,当前线程只有获得了锁,才能调用该组件的 await()方法和 signalAll()方法,功能与任意一个Java对象的wait()、wait(long timeout)、notify() 以及 notifyAll() 方法一致 |
阿里巴巴开发手册总结
在使用阻塞等待获取锁的方式中,必须在 try 代码块之外,并且在加锁方法与 try 代码块之间没有任何可能抛出异常的方法调用,避免加锁成功后,在 finally 中无法解锁。
说明一:如果在 lock 方法与 try 代码块之间的方法调用抛出异常,那么无法解锁,造成其它线程无法成功获取锁。
说明二:如果 lock 方法在 try 代码块之内,可能由于其它方法抛出异常,导致在 finally 代码块中,unlock对未加锁的对象解锁,它会调用 AQS 的 tryRelease 方法(取决于具体实现类),抛出IllegalMonitorStateException 异常。
说明三:在 Lock 对象的 lock 方法实现中可能抛出 unchecked 异常,产生的后果与说明二相同。
正例:
Lock lock = new XxxLock();
// ...
lock.lock();
try {
doSomething();
doOthers();
} finally {
lock.unlock();
}
反例:
Lock lock = new XxxLock();
try {
// 如果此处抛出异常,则直接执行 finally 代码块
doSomething();
// 无论加锁是否成功,finally 代码块都会执行
lock.lock();
doOthers();
} finally {
lock.unlock();
}
在使用尝试机制来获取锁的方式中,进入业务代码块之前,必须先判断当前线程是否持有锁。锁的释放规则与锁的阻塞等待方式相同。
说明:Lock 对象的 unlock 方法在执行时,它会调用 AQS 的 tryRelease 方法(取决于具体实现类),如果当前线程不持有锁,则抛出 IllegalMonitorStateException 异常。
正例:
Lock lock = new XxxLock();
// ...
boolean isLocked = lock.tryLock();
if (isLocked) {
try {
doSomething();
doOthers();
} finally {
lock.unlock();
}
}
重入锁
重入锁 ReentrantLock,它表示该锁能够支持一个线程对资源的重复加锁。该锁还支持获取锁时的公平和非公平性选择。
synchronized 关键字隐式的支持重进入,比如一个 synchronized 修饰的递归方法,在方法执行时,执行线程在获取了锁之后仍嫩连续多次地获得该锁。
ReentrantLock 不是隐式的重进入,而是在调用 lock() 方法时,已经获取到锁的线程,能够再次调用 lock() 方法获取锁而不被阻塞。
公平和非公平选择是指,先对锁进行获取的请求一定先被满足,那么这个锁是公平的,反之,是不公平的。也可以说锁获取是有顺序的。ReentrantLock 提供了一个构造函数,能够控制锁是否是公平的。
事实上,公平的锁机制一般情况下没有非公平的效率高,但是公平锁能够保证等待越久的请求优先得到满足。
读写锁
ReentrantLock 是排他锁,这表示锁在同一时刻只允许一个线程进行访问,而读写锁在同一时刻允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一对锁,一个读和一个写,通过分离读锁和写锁提升性能。
ReadWriteLock 接口仅定义了获取读锁和写锁的两个方法,readLock() 和 writeLock() 。ReentrantReadWriteLock 实现了 ReadWriteLock 的方法,并且提供了便于外界监控其内部工作状态的方法。
Condition 接口
任意一个Java对象,都拥有一组监视器方法(定义在java.lang.Object上),主要包 wait()、wait(long timeout)、notify() 以及 notifyAll() 方法,这些方法与 synchronized 同步关键字配合,可以实现等待/通知模式。Condition 接口也提供了类似 Object 的监视器方法,与 Lock 配合可以实现等待/通知模式,但是这两者在使用方式以及功能特性上还是有差别的。
基于 Lock 实现的生产者消费者 Demo
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ProducerConsumer {
public static void main(String[] args) {
Goods goods = new Goods();
new Thread(new Producer(goods), "生产者---01---").start();
new Thread(new Producer(goods), "生产者---02---").start();
new Thread(new Consumer(goods), "---消费者---01---").start();
new Thread(new Consumer(goods), "---消费者---02---").start();
}
static class Consumer implements Runnable {
private Goods goods;
public Consumer(Goods goods) {
this.goods = goods;
}
public void run() {
while (true) {
try {
goods.get();
} catch (InterruptedException e) {
}
}
}
}
static class Producer implements Runnable {
private Goods goods;
private Producer(Goods goods) {
this.goods = goods;
}
public void run() {
while (true) {
try {
goods.set("商品");
} catch (InterruptedException e) {
}
}
}
}
static class Goods {
private String name;
private int count = 1;
// true:生产的商品还未被消费;false:商品缺货
private boolean flag = false;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void set(String name) throws InterruptedException {
lock.lock();
// 多个生产者,一定要用 while 循环判断,否则会死锁
try {
while (!this.flag) {
condition.await();
}
this.name = name + count++;
this.flag = true;
System.out.println(Thread.currentThread().getName() + this.name);
condition.signalAll();
}finally {
lock.unlock();
}
}
public void get() throws InterruptedException {
lock.lock();
// 多个消费者,一定要用 while 循环判断,否则会死锁
try {
while (!this.flag) {
condition.await();
}
this.flag = false;
System.out.println(Thread.currentThread().getName() + this.name);
condition.signalAll();
}finally {
lock.unlock();
}
}
}
}
下面代码是使用两个 Condition,分别控制生产者和消费者的等待唤醒
static class Goods {
private String name;
private int count = 1;
// true:生产的商品还未被消费;false:商品缺货
private boolean flag = false;
private Lock lock = new ReentrantLock();
// 生产者
private Condition condition_pro = lock.newCondition();
// 消费者
private Condition condition_con = lock.newCondition();
public void set(String name) throws InterruptedException {
lock.lock();
// 多个生产者,一定要用 while 循环判断,否则会死锁
try {
while (!this.flag) {
// 生产者等待
condition_pro.await();
}
this.name = name + count++;
this.flag = true;
System.out.println(Thread.currentThread().getName() + this.name);
// 消费者唤醒
condition_con.signal();
}finally {
lock.unlock();
}
}
public void get() throws InterruptedException {
lock.lock();
// 多个消费者,一定要用 while 循环判断,否则会死锁
try {
while (!this.flag) {
// 消费者等待
condition_con.await();
}
this.flag = false;
System.out.println(Thread.currentThread().getName() + this.name);
// 消费者唤醒
condition_pro.signal();
}finally {
lock.unlock();
}
}
}
基于读写锁的 Demo
public class Cache {
static Map<String, Object> map = new HashMap<String, Object>();
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
static Lock r = rwl.readLock();
static Lock w = rwl.writeLock();
// 获取一个key对应的value
public static final Object get(String key) {
r.lock();
try {
return map.get(key);
} finally {
r.unlock();
}
}
// 设置key对应的value,并返回旧的value
public static final Object put(String key, Object value) {
w.lock();
try {
return map.put(key, value);
} finally {
w.unlock();
}
}
// 清空所有的内容
public static final void clear() {
w.lock();
try {
map.clear();
} finally {
w.unlock();
}
}
}