锁---ReentrantLock(独占锁、可重入锁)

锁—ReentrantLock(独占锁、可重入锁)

************ 如有侵权请提示删除 ***************


### 概念: java除了使用关键字synchronized外,还可以使用ReentrantLock实现独占锁的功能。而且ReentrantLock相比synchronized而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。这篇文章主要是从使用的角度来分析一下ReentrantLock。

简介

ReentrantLock常常对比着synchronized来分析,我们先对比着来看然后再一点一点分析。

(1)synchronized是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。

(2)synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。

(3)synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以相应中断。

ReentrantLock好像比synchronized关键字没好太多,我们再去看看synchronized所没有的,一个最主要的就是ReentrantLock还可以实现公平锁机制。什么叫公平锁呢?也就是在锁上等待时间最长的线程将获得锁的使用权。通俗的理解就是谁排队时间最长谁先执行获取锁

使用

简单使用

我们先给出一个最基础的使用案例,也就是实现锁的功能。

public class TestReentrantLock {
    //非公平锁  等价于new ReentrantLock(false)
    private static final Lock lock = new ReentrantLock();
    public static void main(String[] args) {
        //测试-1   若果注释掉1、2处,未释放锁就可能有线程获取到锁
        new Thread(() ->test(),"线程A").start();
        new Thread(() ->test(),"线程B").start();
        new Thread(() ->test(),"线程C").start();
    }
    public static void test(){
        try {
            lock.lock();  //1
            System.out.println (Thread.currentThread().getName() + "获取了锁");
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println (Thread.currentThread().getName() + "释放了锁");
            lock.unlock();//2
        }
    }
}

输出效果:

//test1效果  非公平锁   cpu时间片轮到哪个线程,哪个线程就能获取锁
//线程A获取了锁
//线程A释放了锁
//线程C获取了锁
//线程C释放了锁
//线程B获取了锁
//线程B释放了锁

在这里我们定义了一个ReentrantLock,然后再test方法中分别lock和unlock,运行一边就可以实现我们的功能。这就是最简单的功能实现,代码很简单。我们再看看ReentrantLock和synchronized不一样的地方,那就是公平锁的实现。

公平锁实现

对于公平锁的实现,就要结合着我们的可重入性质了。公平锁的含义我们上面已经说了,就是谁等的时间最长,谁就先获取锁。

public class TestReentrantLock {
   //实现公平锁机制
    private static final ReentrantLock lock1 = new ReentrantLock(true);
    public static void main(String[] args) {
        //测试-2    公平锁实现
        new Thread(() ->test2(),"线程A").start();
        new Thread(() ->test2(),"线程B").start();
        new Thread(() ->test2(),"线程C").start();
        new Thread(() ->test2(),"线程D").start();
        new Thread(() ->test2(),"线程E").start();
    }
    //公平锁实现
    public static void test2(){
        for (int i = 0; i < 2; i++) {
            try {
                lock1.lock();
                System.out.println (Thread.currentThread().getName() + "获取了锁");
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                lock1.unlock();
            }
        }
    }
}

首先new一个ReentrantLock的时候参数为true,表明实现公平锁机制。在这里我们多定义几个线程ABCDE,然后再test方法中循环执行了两次加锁和解锁的过程。

输出效果:

//test2效果   公平锁
//线程A获取了锁
//线程B获取了锁
//线程C获取了锁
//线程D获取了锁
//线程E获取了锁
//线程A获取了锁
//线程B获取了锁
//线程C获取了锁
//线程D获取了锁
//线程E获取了锁
非公平锁实现

非公平锁那就随机的获取,谁运气好,cpu时间片轮到哪个线程,哪个线程就能获取锁,和上面公平锁的区别很简单,就在于先new一个ReentrantLock的时候参数为false,当然我们也可以不写,默认就是false。直接测试一下
输出效果:

//test2效果   非公平锁  整个过程随机的,没有先后
//线程A获取了锁
//线程B获取了锁
//线程B获取了锁
//线程C获取了锁
//线程C获取了锁
//线程D获取了锁
//线程D获取了锁
//线程E获取了锁
//线程A获取了锁
//线程E获取了锁
响应中断

中断响应是锁申请等待过程中可以放弃等待转去处理中断

响应中断就是一个线程获取不到锁,不会一直等下去,ReentrantLock会给予一个中断回应。在这里我们举一个死锁的案例。

首先我们定义一个测试类ReentrantLockTest3。

public class TestReentrantLock {
    public static void main(String[] args) {
       Thread thread1 = new Thread(new ThreadDemo(1), "thread1");
        Thread thread2 = new Thread(new ThreadDemo(2), "thread2");

        thread1.start();
        thread2.start();
        thread2.interrupt();//中断第一个线程
    }
    static class ThreadDemo implements Runnable{
       //实例化两把锁
        public static ReentrantLock firstLock = new ReentrantLock();
        public static ReentrantLock secondLock = new ReentrantLock();
        int lock = 0;//标识先请求那把锁

        //控制加锁顺序
        public ThreadDemo(int lock) {
            lock = lock;
        }

        @Override
        public void run() {
            try {
                if (lock == 1) {
                    //lockInterruptibly()是对中断进行响应的申请动作
                    firstLock.lockInterruptibly();
                    //假设先获得firstLock锁,睡一秒,再请求secondLock锁
                    TimeUnit.MILLISECONDS.sleep(2000);
                    secondLock.lockInterruptibly();
                    System.out.println(Thread.currentThread().getName()
                            + "执行完成");
                } else {
                    //lockInterruptibly()是对中断进行响应的申请动作
                    secondLock.lockInterruptibly();
                    //假设先获得firstLock锁,睡一秒,再请求secondLock锁
                    TimeUnit.MILLISECONDS.sleep(2000);
                    firstLock.lockInterruptibly();
                    System.out.println(Thread.currentThread().getName()
                            + "执行完成");
                }

            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                //查询当前线程是否保持此锁,此方法通常用于调试和测试
                if (firstLock.isHeldByCurrentThread()) {
                    firstLock.unlock();
                }
                if(secondLock.isHeldByCurrentThread()){
                    secondLock.unlock();
                }
                System.out.println(Thread.currentThread().getName()
                        + "线程退出");
            }
        }
    }
    
}

代码要做的就是实例化两个线程,线程1先申请lock1锁,睡眠一秒后再申请lock2锁;线程2则相反,先申请lock2锁,完了睡眠一秒再申请lock1锁。当两个线程都执行后,会造成死锁,线程1占有lock1锁同时申请lock2锁,线程2占有lock2锁同时申请lock1锁, 此时两个线程产生死锁,不过在主线程中第70行我们对线程2做中断操作,线程2会放弃对锁lock1的申请,同时释放锁lock2,所以线程1会得到锁lock2,并成功退出。

输出结果如下:

thread1执行完成
thread1线程退出
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at com.mmz.tkp.controller.thread.locks.reentrantlock.TestReentrantLock$ThreadDemo.run(TestReentrantLock.java:128)
	at java.lang.Thread.run(Thread.java:748)
thread2线程退出

由输出结果可以看出,线程2没有输出执行完成的语句,也就是说线程2并没有获得锁lock1,在线程2响应中断后,放弃了对锁lock1的申请而直接执行下面的语句,也就是输出退出语句。

线程1获得锁lock1后申请锁lock2,虽然产生了死锁,但线程2后来放弃了锁lock1申请,并释放了锁lock2,所以线程1可以得到锁lock2,输出执行完成的语句。

限时等待

等待限时是线程申请锁时给定一个等待时间,如果等待时间过后线程还没能拿到锁,那么线程就停止等待。

像短作业优先调度算法中有可能出现的饥饿现象,给定锁一个等待限时是很有用的。想要在申请锁时给定一个限时,就要用到重入锁中的方法tryLock(long time,TimeUnit unit),两个参数,一个表示时长,一个表示时间单位,看下面这段代码:

public class TestReentrantLock {
    public static void main(String[] args) {
       ThreadDemo1 threadDemo1 = new ThreadDemo1();
        Thread thread1 = new Thread(threadDemo1,"thread1");
        Thread thread2 = new Thread(threadDemo1,"thread2");

        thread1.start();
        thread2.start();
    }
    static class ThreadDemo1 implements Runnable {
        //实例化一把重入锁
        public static ReentrantLock lock = new ReentrantLock();

        @Override
        public void run() {
            try {
                //尝试获取锁,拿不到3秒后再试
                if (lock.tryLock(3, TimeUnit.SECONDS)) {
                    System.out.println("Thread " + Thread.currentThread().getId() + " get lock successful.");
                    //拿到锁后,睡5秒,也就是占有锁5秒
                    TimeUnit.SECONDS.sleep(5);
                } else {
                    System.out.println("Thread " + Thread.currentThread().getId() + " get lock failed.");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
    }
}

对于上面的代码,tryLock()方法中等待时长为3,时间单位是秒,也就是申请锁等待时间最长为3秒。线程1获得锁lock后会输出成功语句然后睡眠5秒,此时线程2申请锁lock2就会失败,在等待5秒后因为还得不到锁而输出失败语句:

Thread 13 get lock successful.
Thread 14 get lock failed.

ReentrantLock还给我们提供了获取锁限时等待的方法tryLock(),可以选择传入时间参数,表示等待指定的时间, 无参则表示立即返回锁申请的结果: true 表示获取锁成功, false 表示获取锁失败。我们可以使用该方法配合失败重试机制来更好的解决死锁问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值