一. 前言
synchronized 是Java的关键字,是Java的内置特性,在JVM层面实现了对临界资源的同步互斥访问,但 synchronized 粒度有些大,在处理实际问题时存在诸多局限性,比如响应中断等。Lock 提供了比 synchronized更广泛的锁操作,它能以更优雅的方式处理线程同步问题。
二.Lock相关接口
1.lock
void lock();
lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此,一般来说,使用Lock必须在try…catch…块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。通常使用Lock来进行同步的话,是以下面这种形式去使用的:
Lock lock = ...;
lock.lock();
try{
//处理业务逻辑
}catch(Exception ex){
//异常处理
}finally{
//释放锁
lock.unlock();
}
2.lockInterruptibly
当一个线程获取了锁之后,是不会被interrupt()方法中断的。因为interrupt()方法只能中断阻塞过程中的线程而不能中断正在运行过程中的线程。因此,当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,那么只有进行等待的情况下,才可以响应中断的。与 synchronized 相比,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。
void lockInterruptibly() throws InterruptedException;
常用方法:
public void method() throws InterruptedException {
//中断正在等待阻塞中的线程
lock.lockInterruptibly();
try {
//获取锁,处理逻辑
}
finally {
//解锁
lock.unlock();
}
}
3.tryLock
尝试是否能获得锁 如果不能获得立即返回。如果锁未被另一个线程保持,则获取锁。
如果当前线程已经保持此锁,则将保持计数加 1,该方法将返回 true。
如果锁被另一个线程保持,则此方法将立即返回 false 值。
boolean tryLock();
常用方法:
Lock lock = ...;
if (lock.tryLock()) {
try {
// 进行锁状态,执行任务
} finally {
lock.unlock();
}
} else {
// 执行替代动作
}}
应用场景,比如防止重复提交业务
4.tryLock+time
如果在给定的等待时间内是空闲的,当超过时间无获取锁,调用B.interrupt()会被中断等待线程
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
实现可以检测到错误地使用锁,例如将导致调用的死锁,并且可能在这种情况下抛出(未检查)异常。
5.unlock
释放锁
void unlock();
6.newCondition
Condition newCondition();
(1).Condition中的await()方法相当于Object的wait()方法,Condition中的signal()方法相当于Object的notify()方法,Condition中的signalAll()相当于Object的notifyAll()方法。不同的是,Object中的这些方法是和同步锁捆绑使用的;而Condition是需要与互斥锁/共享锁捆绑使用的。
(2).Condition它更强大的地方在于:能够更加精细的控制多线程的休眠与唤醒。对于同一个锁,我们可以创建多个Condition,在不同的情况下使用不同的Condition。
例如,假如多线程读/写同一个缓冲区:当向缓冲区中写入数据之后,唤醒"读线程";当从缓冲区读出数据之后,唤醒"写线程";并且当缓冲区满的时候,"写线程"需要等待;当缓冲区为空时,“读线程"需要等待。
如果采用Object类中的wait(), notify(), notifyAll()实现该缓冲区,当向缓冲区写入数据之后需要唤醒"读线程"时,不可能通过notify()或notifyAll()明确的指定唤醒"读线程”,而只能通过notifyAll唤醒所有线程(但是notifyAll无法区分唤醒的线程是读线程,还是写线程)。 但是,通过Condition,就能明确的指定唤醒读线程。
三.lock与synchronized区分
(1) Lock是一个接口,是JDK层面的实现;而synchronized是Java中的关键字,是Java的内置特性,是JVM层面的实现;
(2) synchronized 在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
(3) Lock 可以让等待锁的线程响应中断,而使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
(4) 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到;
(5) Lock可以提高多个线程进行读操作的效率。
四.案例
1.模拟1000个线程同时对一个变量通过lock对一个变量进行增加操作
static ExecutorService service=Executors.newCachedThreadPool();
static Lock lock = new ReentrantLock();
private static int num;
public static void main(String[] args) {
for (int i=0;i<1000;i++){
service.submit(new Runnable() {
@Override
public void run() {
caculateNum();
}
});
}
}
private static void caculateNum() {
try {
lock.lock();
num++;
}finally {
lock.unlock();
}
System.out.println("num:"+num);
}
结果:最终可输出1000.
…
num:922
num:920
num:918
num:1000
2.通过newCondition模拟多线程并发执行对同一变量进行操作,通过对条件的操控,来对newCondition的认识
static ExecutorService service= Executors.newCachedThreadPool();
static Lock lock = new ReentrantLock();
private static final Condition addCondition = lock.newCondition();
private static final Condition reduceCondition = lock.newCondition();
private static int num;
public static void main(String[] args) {
for (int i=0;i<100;i++){
service.submit(new AddTask());
}
for (int i=0;i<1000;i++){
service.submit(new ReduceTask());
}
}
public static class AddTask implements Runnable{
@Override
public void run() {
try {
lock.lock();
if (num == 5) {
addCondition.await();
}
num++;
System.out.println("线程AddTask===========:"+num);
reduceCondition.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static class ReduceTask implements Runnable{
@Override
public void run() {
try {
lock.lock();
// if (num == 0) {
// reduceCondition.wait();
// }
num--;
addCondition.signal();
System.out.println("线程ReduceTask:"+num);
// } catch (InterruptedException e) {
// e.printStackTrace();
} finally {
lock.unlock();
}
}
}
}
结果
…
线程ReduceTask:-883
线程ReduceTask:-884
线程ReduceTask:-885
线程ReduceTask:-886
线程ReduceTask:-887
线程ReduceTask:-888
线程ReduceTask:-889
线程ReduceTask:-890
线程ReduceTask:-891
线程ReduceTask:-892
线程ReduceTask:-893
线程ReduceTask:-894
线程ReduceTask:-895
线程ReduceTask:-896
线程ReduceTask:-897
线程ReduceTask:-898
线程ReduceTask:-899
线程ReduceTask:-900