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();
    }

}