Java中锁除了synchronized之外,还有ReentrantLock,它们都是独占锁和可重入锁,两者虽然性能上没有什么区别,但ReentrantLock比synchronized功能更丰富灵活,更加适合复杂的并发场景。
什么是独占锁和可重入锁
独占锁就是指该锁一次只能被一个线程所持有。
和独占锁相对应的就是共享锁,共享锁可以被多个线程锁持有,例如ReentrantReadWriteLock的读锁是共享,写锁是独占,写的时候只能一个人写,读的时候可以多个人读。
可重入锁是某个线程已经已经获得某个锁,可以再次获取锁而不会出现死锁,但是需要注意的是必须保证获取的次数和释放的次数一样,否则可能导致其他线程无法获取该锁。
public class ReentrantLockTest {
public static void main(String[] args) throws InterruptedException {
//创建锁
Lock lock = new ReentrantLock();
String threadName = Thread.currentThread().getName() + "线程";
for (int i = 1; i < 4; i++) {
Thread.sleep(1000);
//获取锁
lock.lock();
System.out.println(threadName + "第"+i+"次获取到锁 ");
}
for (int i = 1; i < 4; i++) {
try {
Thread.sleep(1000);
}finally {
//释放锁
lock.unlock();;
System.out.println(threadName + "第"+i+"次释放锁 ");
}
}
}
}
输出:
main线程第1次获取到锁
main线程第2次获取到锁
main线程第3次获取到锁
main线程第1次释放锁
main线程第2次释放锁
main线程第3次释放锁
ReentrantLock实现公平锁和非公平锁
默认是非公平锁
//构造方法,fair=true时是公平锁,默认非公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
演示公平锁,线程轮流获取到锁
classReentrantLockFairTest {
static Lock lock = new ReentrantLock(true);
static class Task implements Runnable {
private int id;
public Task(int id) {
this.id = id;
}
@Override
public void run() {
for(int i=0;i<2;i++){
lock.lock();
System.out.println("获得锁的线程:"+id);
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 1; i < 6; i++) {
new Thread(new Task(i)).start();
}
}
}
输出:
获得锁的线程:1
获得锁的线程:2
获得锁的线程:3
获得锁的线程:4
获得锁的线程:5
获得锁的线程:1
获得锁的线程:2
获得锁的线程:3
获得锁的线程:4
获得锁的线程:5
演示非公平锁,线程会重复获取到锁,如果获取到锁的线程足够多,那么可能会导致某些线程长时间得不到锁
classReentrantLockNoFairTest {
static Lock lock = new ReentrantLock();
static class Task implements Runnable {
private int id;
public Task(int id) {
this.id = id;
}
@Override
public void run() {
for(int i=0;i<2;i++){
lock.lock();
System.out.println("获得锁的线程:"+id);
try {
TimeUnit.MILLISECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
for (int i = 1; i < 6; i++) {
new Thread(new Task(i)).start();
}
}
}
输出:
获得锁的线程:1
获得锁的线程:1
获得锁的线程:2
获得锁的线程:2
获得锁的线程:3
获得锁的线程:3
获得锁的线程:4
获得锁的线程:4
获得锁的线程:5
获得锁的线程:5
大部分情况下我们使用非公平锁,因为其性能会比公平锁要好很多。
死锁
下面的程序会出现死锁,程序会处于死锁状态无法停止
class ReentrantLockInterruptTest {
static Lock lock1 = new ReentrantLock();
static Lock lock2 = new ReentrantLock();
static class Task implements Runnable {
String name;
Lock lock1;
Lock lock2;
public Task(String name, Lock lock1, Lock lock2) {
this.name = name;
this.lock1 = lock1;
this.lock2 = lock2;
}
@Override
public void run() {
try {
lock1.lockInterruptibly();
TimeUnit.MILLISECONDS.sleep(100);
lock2.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock1.unlock();
lock2.unlock();
System.out.println(name+"正常结束!");
}
}
}
public static void main(String[] args) throws InterruptedException {
//该线程先获取锁1,再获取锁2
Thread thread1 = new Thread(new Task("thread1", lock1, lock2));
//该线程先获取锁2,再获取锁1
Thread thread2 = new Thread(new Task("thread2", lock2, lock1));
thread1.start();
thread2.start();
}
}
使用ReentrantLock可响应中断解决死锁问题
synchronized阻塞在锁上的线程除非获得锁否则将一直等待下去,也就是说这种无限等待获取锁的行为无法被中断。而ReentrantLock给我们提供了一个可以响应中断的获取锁的方法lockInterruptibly()。该方法可以用来解决死锁问题。
我们通过中断其中一个线程,结束线程间毫无意义的等待,被中断的线程抛出异常,另外要给线程将会正常结束。
class ReentrantLockInterruptTest {
static Lock lock1 = new ReentrantLock();
static Lock lock2 = new ReentrantLock();
static class Task implements Runnable {
String name;
Lock lock1;
Lock lock2;
public Task(String name, Lock lock1, Lock lock2) {
this.name = name;
this.lock1 = lock1;
this.lock2 = lock2;
}
@Override
public void run() {
try {
lock1.lockInterruptibly();
TimeUnit.MILLISECONDS.sleep(100);
lock2.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock1.unlock();
lock2.unlock();
System.out.println(name+"正常结束!");
}
}
}
public static void main(String[] args) throws InterruptedException {
//该线程先获取锁1,再获取锁2
Thread thread1 = new Thread(new Task("thread1", lock1, lock2));
//该线程先获取锁2,再获取锁1
Thread thread2 = new Thread(new Task("thread2", lock2, lock1));
thread1.start();
thread2.start();
//中断线程1, 如果不中断,程序将处于死锁状态无法停止
thread1.interrupt();
}
}
通过tryLock() + 失败重试机制来解决死锁问题
尝试获取锁,获取锁成功返回true,获取失败返回false
boolean tryLock();
//time: 等待时间
//unit: 时间单位
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
第一次获取锁失败时会休眠10毫秒,然后重新获取,直到获取成功。第二次获取失败时,首先会释放第一把锁,再休眠10毫秒,然后重试直到成功为止。线程获取第二把锁失败时将会释放第一把锁,这是解决死锁问题的关键,避免了两个线程分别持有一把锁然后相互请求另一把锁。
class ReentrantLockTryLockTest {
static Lock lock1 = new ReentrantLock();
static Lock lock2 = new ReentrantLock();
static class Task implements Runnable {
String name;
Lock lock1;
Lock lock2;
public Task(String name, Lock lock1, Lock lock2) {
this.name = name;
this.lock1 = lock1;
this.lock2 = lock2;
}
@Override
public void run() {
try {
while (!lock1.tryLock()){
TimeUnit.MILLISECONDS.sleep(10);
}
while (!lock2.tryLock()){
lock1.unlock();
TimeUnit.MILLISECONDS.sleep(10);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock1.unlock();
lock2.unlock();
System.out.println(name+"正常结束!");
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Task("thread1", lock1, lock2));
Thread thread2 = new Thread(new Task("thread2", lock2, lock1));
thread1.start();
thread2.start();
}
}
输出:
thread1正常结束!
thread2正常结束!
Conditon
使用synchronized结合Object上的wait和notify方法可以实现线程间的等待通知机制。ReentrantLock结合Condition接口同样可以实现这个功能。而且相比前者使用起来更清晰也更简单。
class ConditionDemo1 {
private ReentrantLock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
void method1(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"条件不满足, await.....");
condition.await();
System.out.println(Thread.currentThread().getName()+"条件满足, 开始继续执行任务");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
void method2(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"准备工作完成, 唤醒其他线程");
condition.signal();
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
ConditionDemo1 conditionDemo = new ConditionDemo1();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
conditionDemo.method2();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
conditionDemo.method1();
}
}
输出:
main条件不满足, await.....
Thread-0准备工作完成, 唤醒其他线程
main条件满足, 开始继续执行任务
使用Condition实现生产者消费者模式
class ConditionDemo2 {
private int queueSize = 10;
private PriorityQueue<Integer> queue = new PriorityQueue<>(queueSize);
private Lock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
class Consumer extends Thread{
@Override
public void run() {
while (true){
lock.lock();
try {
while (queue.size() == 0){
System.out.println("队列空了,等待数据中。。。。。");
try {
notEmpty.await();
}catch (Exception e){
e.printStackTrace();
}
}
queue.poll();
notFull.signalAll();
System.out.println("从队列中取走一个数据,队列剩余"+queue.size()+"个元素");
}finally {
lock.unlock();
}
}
}
}
class Producer extends Thread{
@Override
public void run() {
while (true){
lock.lock();
try{
while (queue.size() == queueSize){
System.out.println("队列满, 等待空余。。。。");
try {
notFull.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.offer(1);
notEmpty.signalAll();
System.out.println("向队列插入一个数据, 队列剩余空间"+(queueSize-queue.size()));
}finally {
lock.unlock();
}
}
}
}
public static void main(String[] args) {
ConditionDemo2 conditionDemo2 = new ConditionDemo2();
Producer producer = conditionDemo2.new Producer();
Consumer consumer = conditionDemo2.new Consumer();
producer.start();
consumer.start();
}
}