美文网首页
Java的锁—重入锁(ReentrantLock)

Java的锁—重入锁(ReentrantLock)

作者: kopshome | 来源:发表于2018-07-08 13:51 被阅读0次

    重入锁简单理解就是对同一个线程而言,它可以重复的获取锁。例如这个线程可以连续获取两次锁,但是释放锁的次数也一定要是两次。下面是一个简单例子:

    public class ReenterLock {
    
        private static ReentrantLock lock = new ReentrantLock();
    
        private static int i = 0;
    
        // 循环1000000次
        private static Runnable runnable = () -> IntStream.range(0, 1000000).forEach((j) -> {
            lock.lock();
            try {
                i++;
            } finally {
                lock.unlock();
            }
        });
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread1 = new Thread(runnable);
            Thread thread2 = new Thread(runnable);
            thread1.start();
            thread2.start();
            // 利用join,等thread1,thread2结束后,main线程才继续运行,并打印 i
            thread1.join();
            thread2.join();
            // 利用lock保护的 i,最终结果为 2000000,如果不加,则值肯定小于此数值
            System.out.println(i);
        }
    }
    

    从上面的代码可以看到,相比于synchronized,开发者必须手动指定锁的位置和什么时候释放锁,这样必然增加了灵活性。

    线程中断响应

    如果线程阻塞于synchronized,那么要么获取到锁,继续执行,要么一直等待。重入锁提供了另一种可能,就是中断线程。下面的例子是利用两个线程构建一个死锁,然后中断其中一个线程,使另一个线程获取锁的例子:

    public class ReenterLockInterrupt {
        private static ReentrantLock lock = new ReentrantLock();
    
        private static Runnable runnable = () -> {
            try {
                // 利用 lockInterruptibly 申请锁,这是可以进中断申请的申请锁操作
                lock.lockInterruptibly();
                // 睡眠20秒,在睡眠结束之前,main方法里要中断thread2的获取锁操作
                Thread.sleep(20000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                String threadName = Thread.currentThread().getName();
                // 中断后抛出异常,最后要释放锁
                // 如果是线程1则释放锁,因为线程2就没拿到锁,所以不用释放
                if ("Thread-1".equals(threadName)) lock.unlock();
                System.out.println(threadName+" 停止");
            }
        };
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(runnable, "thread-1");
            Thread thread2 = new Thread(runnable, "thread-2");
            thread1.start();
    
            // 让主线程停一下,让thread1获取锁后再启动thread2
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // 这里什么也不做
            }
    
            thread2.start();
            thread2.interrupt();
        }
    }
    

    thread-1拿到锁之后,线程即持有锁并等待20秒,然后thread-2启动,并没有拿到锁,这时候中断thread-2线程,线程2退出。

    有限时间的等待锁

    顾名思义,简单理解就是在指定的时间内如果拿不到锁,则不再等待锁。当持有锁的线程出问题导致长时间持有锁的时候,你不可能让其他线程永远等待其释放锁。下面是一个例子:

    public class ReenterTryLock {
        private static ReentrantLock reenterLock = new ReentrantLock();
    
        private static Runnable runnable = () -> {
            try {
                // tryLock()方法会返回一个布尔值,获取锁成功则为true
                if (reenterLock.tryLock(3, TimeUnit.SECONDS)) {
                    Thread.sleep(5000);
                } else {
                    System.out.println(Thread.currentThread().getName() + "获取锁失败");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                // 最后,如果当前前程在持有锁,则释放锁
                if (reenterLock.isHeldByCurrentThread()) {
                    System.out.println(Thread.currentThread().getName() + "释放锁了");
                    reenterLock.unlock();
                }
            }
        };
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(runnable, "thread-1");
            Thread thread2 = new Thread(runnable, "thread-2");
    
            thread1.start();
            thread2.start();
        }
    }
    

    这里使用tryLock()第一个获取锁的线程,会停止5秒。而获取锁的设置为3秒获取不到锁则放弃,所以第二个去尝试获取锁的线程是获取不到锁而被迫停止的。如果tryLock()方法不传入任何参数,那么获取锁的线程不会等待锁,则立即返回false。

    公平锁与非公平锁

    当一个线程释放锁时,其他等待的线程则有机会获取锁,如果是公平锁,则分先来后到的获取锁,如果是非公平锁则谁抢到锁算谁的,这就相当于排队买东西和不排队买东西是一个道理。Java的synchronized关键字就是非公平锁

    那么重入锁ReentrantLock()是公平锁还是非公平锁?

    重入锁ReentrantLock()是可以设置公平性的,可以参考其构造方法:

    // 通过传入一个布尔值来设置公平锁,为true则是公平锁,false则为非公平锁
    public ReentrantLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
        }
    

    构建一个公平锁需要维护一个有序队列,如果实际需求用不到公平锁则不需要使用公平锁。下面用一个例子来演示公平锁与非公平锁的区别:

    public class ReenterTryLockFair {
        // 分别设置公平锁和非公平锁,分析打印结果
        private static ReentrantLock lock = new ReentrantLock(true);
    
        private static Runnable runnable = () -> {
            while (true) {
                try {
                    lock.lock();
                    System.out.println(Thread.currentThread().getName() + " 获取了锁");
                } finally {
                    lock.unlock();
                }
            }
        };
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(runnable, "thread---1");
            Thread thread2 = new Thread(runnable, "thread---2");
            Thread thread3 = new Thread(runnable, "thread---3");
    
            thread1.start();
            thread2.start();
            thread3.start();
        }
    }
    

    当设置为true即公平锁的时候,可以看到打印非常规律,截取一段儿打印结果:

    thread---1 获取了锁
    thread---2 获取了锁
    thread---3 获取了锁
    thread---1 获取了锁
    thread---2 获取了锁
    thread---3 获取了锁
    thread---1 获取了锁
    thread---2 获取了锁
    thread---3 获取了锁
    thread---1 获取了锁
    thread---2 获取了锁
    thread---3 获取了锁
    thread---1 获取了锁
    thread---2 获取了锁
    thread---3 获取了锁
    thread---1 获取了锁
    thread---2 获取了锁
    thread---3 获取了锁
    

    可以看到,都是thread--1,thread--2,thread--3,无限循环下去,如果设置的为非公平锁,打印结果就混乱没有规律了:

    thread---3 获取了锁
    thread---3 获取了锁
    thread---3 获取了锁
    thread---3 获取了锁
    thread---3 获取了锁
    thread---3 获取了锁
    thread---3 获取了锁
    thread---3 获取了锁
    thread---3 获取了锁
    thread---3 获取了锁
    thread---3 获取了锁
    thread---3 获取了锁
    thread---3 获取了锁
    thread---3 获取了锁
    thread---2 获取了锁
    thread---2 获取了锁
    thread---2 获取了锁
    thread---2 获取了锁
    thread---2 获取了锁
    thread---2 获取了锁
    thread---2 获取了锁
    thread---2 获取了锁
    thread---2 获取了锁
    thread---2 获取了锁
    thread---1 获取了锁
    

    Condition

    同jdk中的等待/通知机制类似,只不过Condition是用在重入锁这里的。有了Condition,线程就可以在合适的时间等待,在合适的时间继续执行。

    Condition接口包含以下方法:

    // 让当前线程等待,并释放锁
    void await() throws InterruptedException;
    // 和await类似,但在等待过程中不会相应中断
    void awaitUninterruptibly();
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    boolean awaitUntil(Date deadline) throws InterruptedException;
    // 唤醒等待中的线程
    void signal();
    // 唤醒等待中的所有线程
    void signalAll();
    

    下面是一个简单示例:

    public class ReenterLockCondition {
        private static ReentrantLock lock = new ReentrantLock();
    
        private static Condition condition = lock.newCondition();
    
        private static Runnable runnable = () -> {
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + "进入等待。。");
                condition.await();
                System.out.println(Thread.currentThread().getName() + "继续执行");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        };
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(runnable, "thread--1");
            thread.start();
    
            Thread.sleep(2000);
    
            lock.lock();
            condition.signal();
            System.out.println("主线程发出信号");
            lock.unlock();
        }
    }
    

    thread--1启动,拿到锁,然后进入等待并且释放锁,2秒后,主线程拿到锁,然后发出信号并释放锁,最后,thread--1继续执行。下面是打印结果:

    thread--1进入等待。。
    主线程发出信号
    thread--1继续执行
    

    相关文章

      网友评论

          本文标题:Java的锁—重入锁(ReentrantLock)

          本文链接:https://www.haomeiwen.com/subject/uxdouftx.html