当前位置: 首页>前端>正文

Retrantlock使用

ReentrantLock 实现的前提就是 AbstractQueuedSynchronizer,简称 AQS,是
java.util.concurrent 的核心,CountDownLatch、FutureTask、Semaphore、
ReentrantLock 等都有一个内部类是这个抽象类的子类。由于 AQS 是基于 FIFO
队列的实现,因此必然存在一个个节点,Node 就是一个节点,Node 有两种模
式:共享模式和独占模式。ReentrantLock 是基于 AQS 的,AQS 是 Java 并发包
中众多同步组件的构建基础,它通过一个 int 类型的状态变量 state 和一个 FIFO
队列来完成共享资源的获取,线程的排队等待等。AQS 是个底层框架,采用模
板方法模式,它定义了通用的较为复杂的逻辑骨架,比如线程的排队,阻塞,唤
醒等,将这些复杂但实质通用的部分抽取出来,这些都是需要构建同步组件的使
用者无需关心的,使用者仅需重写一些简单的指定的方法即可(其实就是对于共
享变量 state 的一些简单的获取释放的操作)。

使用Retrantlock多线程操作变量简单示例:
public class ReentrantLockUse extends Thread {
    public static int sum = 0;
    public static ReentrantLock lock = new ReentrantLock();

    public ReentrantLockUse(@NonNull String name) {
        super.setName(name);
    }

    @Override
    public void run() {
        for (int i=0;i<1000;i++) {
            lock.lock();
            try {
                Log.i(TAG, getName() + ": " + sum);
                sum++;
            } finally {
                lock.unlock();
            }
        }
    }
}

开启三个线程操作sum:

        ReentrantLockUse("thread1").start()
        ReentrantLockUse("thread2").start()
        ReentrantLockUse("thread3").start()

最后的结果会是 2000;如果去掉锁,那么输出结果是一个小于2000的不确定的数,这个应该知道因为可能多个线程同步执行某个步骤。

Reentrantlock实现单例

实现线程同步锁synchronized 功能:

public class Singleton {
    private static Singleton instance;
    private static Lock lock = new ReentrantLock();

    public static Singleton getInstance() {
        lock.lock();

        try {
            if (instance == null) {
                instance = new Singleton();
            }
        } finally {
            lock.unlock();
        }

        return instance;
    }
}
ReentrantLock与synchronized对比的优点:

1.可重入性
ReentrantLock和synchronized都是可重入的。synchronized因为可重入因此可以放在被递归执行的方法上,且不用担心线程最后能否正确释放锁;而ReentrantLock在重入时要却确保重复获取锁的次数必须和重复释放锁的次数一样,否则可能导致其他线程无法获得该锁。

2.锁的实现方式
synchronized加锁解锁的过程是隐式的,用户不用手动操作,系统去调度实现,优点是操作简单,但显得不够灵活。一般并发场景使用synchronized的就够了;ReentrantLock需要手动加锁和解锁,且解锁的操作尽量要放在finally代码块中,保证线程正确释放锁。ReentrantLock操作较为复杂,但是因为可以手动控制加锁和解锁过程,在复杂的并发场景中能派上用场。

3.公平性
ReentrantLock提供了公平锁和非公平锁两种API,开发人员完全可以根据应用场景选择锁的公平性;
synchronized是作为Java关键字是依赖于JVM实现,Java团队应该是优先考虑性能问题,因此synchronized是非公平锁。

即相比于synchronized,ReentrantLock在功能上更加丰富,它具有可重入、可中断、可限时、公平锁等特点。

可重入性:
    public static void relock() {
        ReentrantLock lock = new ReentrantLock();

        for (int i=0;i<3;i++) {
            lock.lock();
        }

        for (int i=0;i<3;i++) {
            lock.unlock();
        }
    }

上面的代码通过lock()方法先获取锁三次,然后通过unlock()方法释放锁3次,程序可以正常退出。由于ReentrantLock是重入锁,所以可以反复得到相同的一把锁,它有一个与锁相关的获取计数器state,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放(重入锁)。

可中断

与synchronized不同的是,ReentrantLock对中断是有响应的.synchronized一旦尝试获取锁就会一直等待直到获取到锁。使用ReentrantLock可通过中断来避免死锁。

可超时放弃

定时操作的例如:错误日志、定时过期缓存清理的操作,遇到优先级更高的操作占用资源时,暂时放弃本次操作下次再处理,可以起到让出CPU,提升用户体验;且超时不能获得锁,就返回false,不会永久等待构成死锁;
使用lock.tryLock(long timeout, TimeUnit unit)来实现可限时锁,参数为时间和单位。

    public static void tryLock() {
        ReentrantLock lock = new ReentrantLock();
        try {
            if (lock.tryLock(10000, TimeUnit.SECONDS)) {
                //TO DO

            }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }
可实现公平锁

公平锁: 是指多个线程竞争同一资源时[等待同一个锁时],获取资源的顺序是按照申请锁的先后顺序的;公平锁保障了多线程下各线程获取锁的顺序,先到的线程优先获取锁,有点像早年买火车票一样排队早的人先买到火车票;
基本特点: 线程执行会严格按照顺序执行,等待锁的线程不会饿死,但 整体效率相对比较低;

非公平锁: 是指多个线程竞争同一资源时,获取资源的顺序是不确定的,一般是抢占式的;非公平锁相对公平锁是增加了获取资源的不确定性,但是整体效率得以提升;
基本特点: 整体效率高,线程等待时间片具有不确定性;

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

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

参考
Java中ReentrantLock的使用
ReentrantLock(重入锁)功能详解和应用演示


https://www.xamrdz.com/web/2sb1995129.html

相关文章: