Java并发编程系列——ReentrantLock

基本介绍

  1. ReentrantLock是可重入锁,支持当前线程重入;
  2. ReentrantLock可以等待中断、可以实现公平锁、可以绑定多个条件;
  3. ReentrantLock和synchronized一样属于互斥锁,synchronized是非公平锁,默认情况下ReentrantLock也是不公平的,但可以通过构造函数入参设置为公平锁;
  4. ReentrantLock通过lock()方法获得锁,通过unlock()方法释放锁;
  5. ReentrantLock除了lock()方法获得锁,还可以使用tryLock()方法设置超时时间、使用lockInterruptibly()在等待锁期间响应中断;而synchronized则是一直等待无法响应中断;
  6. ReentrantLock可以使用newCondition()方法创建condition对象,来绑定子条件实现多条件关联;

ReentrantLock的缺点

  1. 在JDK1.5及之前,ReentrantLock能提供比synchronized更好的竞争性能;在JDK1.6及之后,synchronized使用的算法与ReentrantLock中的算法相似,两者性能差距不大;
  2. 需要由使用者来自行释放锁,且最好是在finally代码块中释放,使用者可能会忘记,所以无法保证安全性;
  3. synchronized还有一些优化措施,如线程封闭对象锁消除、增加锁粒度等优化措施等,可能会比使用ReentrantLock显示锁更优;

注意:ReentrantLock无法完全替代synchronized,只有在synchronized无法满足使用要求时(如获取锁超时、获取锁时响应中断等)才应该使用ReentrantLock。

ReentrantLock的使用

1. 使用ReentrantLock作为简单互斥锁

使用ReentrantLock作为竞态条件,获得锁则将count累加一次。

public class ReentrantLockTest {

    private static final ReentrantLock lock = new ReentrantLock();

    private static volatile int count = 0;

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            while (getCount("thread1") < 10) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        Thread thread2 = new Thread(() -> {
            while(getCount("thread2") < 10) {
                try {
                    Thread.sleep(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread1.start();
        thread2.start();
    }

    private static int getCount(String threadName) {
        try {
            lock.lock();
            count++;
            System.out.println(threadName + "得到锁" + count);
            return count;
        } finally {
            lock.unlock();
        }
    }

}

输出:
thread1得到锁1
thread2得到锁2
thread1得到锁3
thread2得到锁4
thread2得到锁5
thread1得到锁6
thread1得到锁7
thread2得到锁8
thread2得到锁9
thread1得到锁10
thread2得到锁11

2. 使用ReentrantLock和condition唤醒线程

  1. 子线程获取锁,调用condition.await()时会释放锁并进入轮询状态(类似synchronized和object.wait()但是并不阻塞)。
  2. 当主线程调用condition.signal()后,并未立即释放锁,最终在主线程释放锁之后,轮询中子线程重新获得锁并在完成输出后再次释放锁。
public class ReentrantLockTest {

    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Condition condition = lock.newCondition();
        new Thread(() -> {
            try {
                System.out.println("子线程等待获取lock,当前持有锁线程数:" + lock.getHoldCount());
                lock.lock();
                System.out.println("子线程await开始,当前持有锁线程数:" + lock.getHoldCount());
                condition.await();
                System.out.println("子线程await结束,当前持有锁线程数:" + lock.getHoldCount());
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }).start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        try {
            System.out.println("main线程等待获取lock,当前持有锁线程数:" + lock.getHoldCount());
            lock.lock();
            System.out.println("main线程获取lock成功,signal开始,当前持有锁线程数:" + lock.getHoldCount());
            condition.signal();
            System.out.println("main线程signal结束,当前持有锁线程数:" + lock.getHoldCount());
        } finally {
            lock.unlock();
        }
    }

}

输出:
子线程等待获取lock,当前持有锁线程数:0
子线程await开始,当前持有锁线程数:1
main线程等待获取lock,当前持有锁线程数:0
main线程获取lock成功,signal开始,当前持有锁线程数:1
main线程signal结束,当前持有锁线程数:1
子线程await结束,当前持有锁线程数:1

3. 使用ReentrantLock和condition实现简单多主题队列

  1. 主线程创建两个condition,两个子线程分别await两个condition;
  2. 主线程随机调用condition通知子线程进行消息处理。
public class ReentrantLockTest {

    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Condition condition1 = lock.newCondition();
        Condition condition2 = lock.newCondition();
        Thread thread1 = new Thread(() -> consumer(condition1, "condition1"));
        thread1.setName("线程1");
        Thread thread2 = new Thread(() -> consumer(condition2, "condition2"));
        thread2.setName("线程2");

        thread1.start();
        thread2.start();

        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        for (int i = 0; i < 10; i++) {
            try {
                lock.lock();
                if (Math.random() < 0.5) {
                    System.out.print(i + "\t激活condition1消费者 → ");
                    condition1.signal();
                } else {
                    System.out.print(i + "\t激活condition2消费者 → ");
                    condition2.signal();
                }
            } finally {
                lock.unlock();
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

    private static void consumer(Condition condition, String msg) {
        while (true) {
            try {
                lock.lock();
                condition.await();
                System.out.println("\t" + Thread.currentThread().getName() + "处理" + msg + "消息");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }

}

输出:
0    激活condition1消费者 →     线程1处理condition1消息
1    激活condition2消费者 →     线程2处理condition2消息
2    激活condition2消费者 →     线程2处理condition2消息
3    激活condition1消费者 →     线程1处理condition1消息
4    激活condition1消费者 →     线程1处理condition1消息
5    激活condition1消费者 →     线程1处理condition1消息
6    激活condition1消费者 →     线程1处理condition1消息
7    激活condition1消费者 →     线程1处理condition1消息
8    激活condition2消费者 →     线程2处理condition2消息
9    激活condition2消费者 →     线程2处理condition2消息

欢迎扫码关注我微信公众号:magicTan。

图片描述

你可能感兴趣的:(java)