Java中的Lock与synchronized的区别
先弄清一个问题:
一、什么是可重入锁?
可重入锁就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁。
看一个synchronized的例子:
package com.pipi.juc;
public class WhatReentrant {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
synchronized (this) {
System.out.println("第1次获取锁,这个锁是:" + this);
int index = 1;
while (true) {
synchronized (this) {
System.out.println("第" + (++index) + "次获取锁,这个锁是:" + this);
}
if (index == 8) {
break; // 跳出死循环
}
}
}
}
}, "t1").start();
}
}
运行结果:
第1次获取锁,这个锁是:com.pipi.juc.WhatReentrant$1@54005b14
第2次获取锁,这个锁是:com.pipi.juc.WhatReentrant$1@54005b14
第3次获取锁,这个锁是:com.pipi.juc.WhatReentrant$1@54005b14
第4次获取锁,这个锁是:com.pipi.juc.WhatReentrant$1@54005b14
第5次获取锁,这个锁是:com.pipi.juc.WhatReentrant$1@54005b14
第6次获取锁,这个锁是:com.pipi.juc.WhatReentrant$1@54005b14
第7次获取锁,这个锁是:com.pipi.juc.WhatReentrant$1@54005b14
第8次获取锁,这个锁是:com.pipi.juc.WhatReentrant$1@54005b14
Process finished with exit code 0
我们发现程序正常运行,说明 synchronized 就是可重入锁。
我们再看个例子:
package com.pipi.juc;
import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class WhatReentrant {
public static void main(String[] args) {
final Lock lock = new ReentrantLock(); // 声明锁对象
new Thread(() -> {
lock.lock(); // 第一次锁
try {
System.out.println("第1次获取锁,这个锁是:" + lock);
int index = 1;
while (true) {
lock.lock(); // 循环内多次获得锁
try {
System.out.println("第" + (++index) + "次获取锁,这个锁是:" + lock);
try { TimeUnit.MILLISECONDS.sleep(new Random().nextInt(200)); } catch (InterruptedException e) { e.printStackTrace(); }
if (index == 8) break; // 跳出循环
} finally {
lock.unlock(); // 每循环1次,解锁1次
}
}
} finally {
lock.unlock(); // 对应第1次上锁,释放掉
}
}, "t1").start();
}
}
第1次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@5103fb35[Locked by thread t1]
第2次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@5103fb35[Locked by thread t1]
第3次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@5103fb35[Locked by thread t1]
第4次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@5103fb35[Locked by thread t1]
第5次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@5103fb35[Locked by thread t1]
第6次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@5103fb35[Locked by thread t1]
第7次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@5103fb35[Locked by thread t1]
第8次获取锁,这个锁是:java.util.concurrent.locks.ReentrantLock@5103fb35[Locked by thread t1]
Process finished with exit code 0
我们发现程序也正常运行,说明 ReentrantLock 也是可重入锁。
我们得出第一个结论:
Lock是一个接口,synchronized是java中的一个关键字。
Lock 和 synchronized都是可重入锁。
2、Lock要我们手动写代码释放锁,synchronized是线程执行完自动释放锁。
我们让Lock上锁2次,释放锁1次,让另一个线程再去调用:
package com.pipi.juc;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class WhatReentrant {
public static void main(String[] args) {
final Lock lock = new ReentrantLock();
new Thread(() -> {
lock.lock();
lock.lock(); // 锁了2次
try {
try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
System.out.println("lock()两次,unlock()一次");
} finally {
lock.unlock(); // 这里释放1次锁
}
}, "t1").start();
new Thread(() -> {
lock.lock(); // 另外的线程,获取锁
try {
System.out.println("我这句话能输出吗?");
} finally {
lock.unlock();
}
}, "t2").start();
}
}
运行结果,我们发现第二个线程的打印一直处于阻塞等待状态。
我们unlock()两次,再次运行,发现程序正常输出。
所以,我们得出第二个结论:
Lock需要程序员手动释放锁,不然可能出现死锁情况。
3、我们再看下Lock接口的源码:
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}
方法解释:
- lock()是我们平时用的最多的,最用是用来获取锁,如果锁已经被其他线程获取,那么就等待。
- tryLock()是一个boolean类型的方法,当调用这个方法的时候,线程会去尝试获取锁,如果获取到的话会返回true,如果获取不到返回false,也就是说这个方法会立马返回一个结果,线程不会等待。
- tryLock(long time, TimeUnit unit)是上面tryLock()方法的一个重载方法,加了两个参数,给定了等待的时间,如果在规定时间拿到锁返回true,如果拿不到返回false。
- lockInterruptibly()就比较特殊了,它表示可被中断的,意思就是,当尝试获取锁的时候,如果获取不到的话就会等待,但是,在等待的过程中它是可以响应中断的,也就是中断线程的等待过程。
- unlock(),释放锁。
- newCondition(),返回Condition对象,用于线程之间的通信,Condition的await(),signalAll()方法,相当于Object的wait(),notifyAll()方法。
结论:
synchronized是Java中的一个关键字,而Lock是Java1.5后在java.util.concurrent.locks包下提供的一种实现同步的方法,那么显然的,synchronized一定是有什么做不到的或者缺陷,才导致了Lock的诞生。
synchronized是关键字,Lock是接口,二者均可实现线程同步;
Lock 和 synchronized都是可重入锁;
synchronized执行完自动释放锁,Lock需要程序员手动释放锁,不然可能出现死锁情况;
使用synchronized线程不释放锁,其他线程会一直等待下去,直到使用完释放或者出现异常,而Lock可以使用可以响应中断的锁或者使用规定等待时间的锁;
synchronized无法得知是否获取到锁,而Lcok可以做到。
所以综上所述:Lock的性能会远高于synchronized。