(十六)ReentrantLock可重入锁使用和介绍

本专栏多线程目录:

(一)线程是什么

(二)Java线程与系统线程和生命周期

(三)Java线程创建方式

(四)为什么要使用线程池

(五)四种线程池底层详解

(六)ThreadPoolExecutor自定义线程池

(七)线程池的大小如何确定

(八)Callable和Runnable的区别

(九)线程池异常捕获

(十)线程池参数——workQueue用法

(十一)sleep(1)、sleep(0)和sleep(1000)的区别

(十二)yield、notify、notifyAll、sleep、join、wait的区别

(十三)synchronized用法,四种锁范围

(十四)volatile的用法,原子性问题

(十五)ThreadLocal的用法,如何解决内存泄漏

(十六)ReentrantLock可重入锁使用和介绍

(十七)AtomicInteger原子类的介绍和使用

(十八)Worker线程管理


1、ReentrantLock介绍

jdk中独占锁的实现除了使用关键字synchronized外,还可以使用ReentrantLock

虽然在性能上ReentrantLocksynchronized没有什么区别,但ReentrantLock相比synchronized而言功能更加丰富,使用起来更为灵活,也更适合复杂的并发场景。

两者的相同点:

1、ReentrantLock和synchronized都是独占锁,只允许线程互斥的访问临界区。

但是实现上两者不同:

synchronized加锁解锁的过程是隐式的,用户不用手动操作,优点是操作简单,但显得不够灵活。一般并发场景使用synchronized的就够了;

ReentrantLock需要手动加锁和解锁,且解锁的操作尽量要放在finally代码块中,保证线程正确释放锁。ReentrantLock操作较为复杂,但是因为可以手动控制加锁和解锁过程,在复杂的并发场景中能派上用场。

2、ReentrantLock和synchronized都是可重入的。

synchronized因为可重入因此可以放在被递归执行的方法上,且不用担心线程最后能否正确释放锁;

ReentrantLock在重入时要却确保重复获取锁的次数必须和重复释放锁的次数一样,否则可能导致其他线程无法获得该锁。

不同点:

1、ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。

2、ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。

3、ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。

2、ReentrantLock的额外功能

公平锁是指当锁可用时,在锁上等待时间最长的线程将获得锁的使用权。(保证)

非公平锁则随机分配这种使用权。

和synchronized一样,默认的ReentrantLock实现是非公平锁,因为相比公平锁,非公平锁性能更好。当然公平锁能防止饥饿,某些情况下也很有用。

在创建ReentrantLock的时候通过传进参数true创建公平锁,如果传入的是false或没传参数则创建的是非公平锁

//公平锁
ReentrantLock lock = new ReentrantLock(true);

上个例子:

public class FairReentrantLock {
  //   static Lock lock = new ReentrantLock(true);
    static Lock lock = new ReentrantLock(false);
    public static void main(String[] args) {
        myThreadDemo[] threadDemos = new myThreadDemo[10];
        for (int i = 0; i < 5; i++) {
            threadDemos[i] = new myThreadDemo(i);
        }
        for (int i = 0; i < 5; i++) {
            threadDemos[i].start();
        }
    }

    static class myThreadDemo extends Thread {
        int threadId;

        myThreadDemo(int threadId) {
            this.threadId = threadId;
        }

        @Override
        public void run() {
            try {
                Thread.sleep(1 * 100);
                for (int i = 0; i < 3; i++) {
                    lock.lock();
                    System.out.println("当前获得锁的线程--->>>" + threadId);
                    lock.unlock();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

输出:

可以看到非公平锁,几乎是一个线程同时获取锁后再到下一个线程执行。

如果申请获取锁的线程足够多,那么可能会造成某些线程长时间得不到锁。这就是非公平锁的“饥饿”问题。

如果换成公平锁:

static Lock lock = new ReentrantLock(true);

可以看到,看起来就有那么一点打乱的顺序,系统会公平地分配资源给每个线程,而不是一个线程一直霸占着,线程几乎是轮流的获取到了锁。

3、ReentrantLock可响应中断问题

synchronized死锁例子:

class SynchronizedDeadLock implements Runnable {
    private String lockA;
    private String lockB;

    public SynchronizedDeadLock(String lockA, String lockB) {
        this.lockA = lockA;
        this.lockB = lockB;
    }

    @Override
    public void run() {
        synchronized (lockA) {
            System.out.println(Thread.currentThread().getName() + "\t 自己持有:" + lockA + "\t 尝试获得:" + lockB);
            //至关重要是这个sleep,因为这里睡眠是为了让 第二个线程有机会进来
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB) {
                System.out.println(Thread.currentThread().getName() + "\t 自己持有:" + lockB + "\t 尝试获得:" + lockA);
            }
        }
    }
}

public class DeadLockDemo {

    public static void main(String[] args) throws InterruptedException {
        String lockA = "locka";
        String lockB = "lockb";
        new Thread(new SynchronizedDeadLock(lockA, lockB), "Thread1").start();
        new Thread(new SynchronizedDeadLock(lockB, lockA), "Thread2").start();
    }
}

ReentrantLock死锁例子:

public class ReentrantLockDeadLock {
    static Lock lock1 = new ReentrantLock();
    static Lock lock2 = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new DeadLockDemo(lock1, lock2), "Thread1");
        Thread thread2 = new Thread(new DeadLockDemo(lock2, lock1), "Thread2");
        thread1.start();
        thread2.start();
    }

    static class DeadLockDemo implements Runnable {
        Lock lockA;
        Lock lockB;

        public DeadLockDemo(Lock lockA, Lock lockB) {
            this.lockA = lockA;
            this.lockB = lockB;
        }

        @Override
        public void run() {
            try {
                lockA.lock();
//                lockA.lockInterruptibly();
                System.out.println(Thread.currentThread().getName() + "\t 自己持有:" + lockA + "\t 尝试获得:" + lockB);
                TimeUnit.SECONDS.sleep(2);
                lockB.lock();
//                lockB.lockInterruptibly();
                System.out.println(Thread.currentThread().getName() + "\t 自己持有:" + lockB + "\t 尝试获得:" + lockA);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lockA.unlock();
                lockB.unlock();
                System.out.println(Thread.currentThread().getName() + "正常结束!");
            }
        }
    }
}

以上这两个例子都会发生死锁,它们的资源竞争是这样的:

Thread1线程执行,首先获取lockA资源,上锁,然后休眠2秒…

Thread2线程执行,然后获取lockB资源,上锁,然后休眠2秒…

Thread1线程醒来,获取lockB资源,发现被锁住了,只能等待…

Thread2线程醒来,获取lockA资源,发现被锁住了,只能等待…

这样,死锁出现了…

ReentrantLock可响应中断:

ReentrantLock的优点还是有的,它提供了lockInterruptibly()方法,用于感知线程中断,从而退出程序。

public class ReentrantLockDeadLock {
    static Lock lock1 = new ReentrantLock();
    static Lock lock2 = new ReentrantLock();

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(new DeadLockDemo(lock1, lock2), "Thread1");
        Thread thread2 = new Thread(new DeadLockDemo(lock2, lock1), "Thread2");
        thread1.start();
        thread2.start();

        Thread.sleep(5 * 1000);
        if (Thread.activeCount() >= 4) {
            thread1.interrupt();//让thread1线程中断
        }
    }

    static class DeadLockDemo implements Runnable {
        Lock lockA;
        Lock lockB;

        public DeadLockDemo(Lock lockA, Lock lockB) {
            this.lockA = lockA;
            this.lockB = lockB;
        }

        @Override
        public void run() {
            try {
//                lockA.lock();
                lockA.lockInterruptibly();
                System.out.println(Thread.currentThread().getName() + "\t 自己持有:" + lockA + "\t 尝试获得:" + lockB);
                TimeUnit.SECONDS.sleep(2);
//                lockB.lock();
                lockB.lockInterruptibly();
                System.out.println(Thread.currentThread().getName() + "\t 自己持有:" + lockB + "\t 尝试获得:" + lockA);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lockA.unlock();
                lockB.unlock();
                System.out.println(Thread.currentThread().getName() + "正常结束!");
            }
        }
    }
}

结果:

程序在休眠5秒后,假如Thread.activeCount() >= 4,主线程、守护线程、Thread1、Thread2 四个都在, 表示死锁发生了。

通过 thread1.interrupt(),中断了Thread1lockInterruptibly()方法 会释放lockAlockb的锁,即 lockA.unlock()lockB.unlock()Thread2感知了,就可以获取loackB的资源,即可以上锁,然后正常退出。

所以ReentrantLock相比synchronized的优势就是:无限等待获取锁的行为可以被中断

4、ReentrantLock锁限时等待

线程中断不是处理死锁特别好的方法,万一线程真的是执行了很久,而不是死锁了,如果贸然中断,可不是一个明智的处理方法。

ReentrantLock 提供了一个tryLock() 方法,可以指定获取锁的等待时间。

tryLock()如果拿到锁就返回true,否则返回false,不会像lock那样无限等待。

if (!lockA.tryLock(2, TimeUnit.SECONDS)) {
    System.out.println(Thread.currentThread().getName() + " 正在等待锁......");
} else {
    System.out.println(Thread.currentThread().getName() + " 拿到了锁");
}

tryLock()也会有死锁的情况,所以为了避免死锁,一个线程不要获取多个锁。

5、Condition

synchronized可以结合Object进行线程之间的通信,比如说waitnotify实现线程的等待和唤醒。

ReentrantLock也有,Java提供了Condition 接口,可以实现ReentrantLock线程之间的通信。

eg:

public class ConditionTest {
    static ReentrantLock lock = new ReentrantLock();
    //通过ReentrantLock创建Condition实例,并与之关联
    static Condition condition = lock.newCondition();

    public static void main(String[] args) throws InterruptedException {
        System.out.println("主线程执行ing...");
        new Thread(new AwaitThread()).start();
        try {
            Thread.sleep(2000);
            lock.lock();
            condition.signal();
        } finally {
            lock.unlock();
        }
        System.out.println("主线程执行结束");
    }

    static class AwaitThread implements Runnable {
        @Override
        public void run() {

            System.out.println("子线程执行ing...");
            lock.lock();
            try {
                System.out.println("子线程停止了");
                condition.await();
                System.out.println("子线程恢复执行了");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}

结果:

主线程执行ing...
子线程执行ing...
子线程停止了
主线程执行结束
子线程恢复执行了

总结:

ReentrantLock比起synchronized功能更加丰富,支持公平锁和非公平锁,而且提供了响应中断。

而且提供了tryLock()锁限时等待,相比synchronized要更灵活。


参考:

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 5
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

醋酸菌HaC

请我喝杯奶茶吧

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值