AQS同步组件--ReentrantLock与锁

栏目: Java · 发布时间: 4年前

内容简介:我们首先使用 private final static Lock lock = new ReentrantLock()声明一个所得实例,然后使用进行加锁和解锁操作。我们在通过一个例子来看看这个ReentrantReadWriteLock怎么用。

ReentrantLock与锁

Synchronized和ReentrantLock异同

  • 可重入性:两者都具有可重入性
  • 锁的实现:Synchronized是依赖jvm实现的,ReentrantLock是jdk实现的。(我们可以理解为一个是操作系统层面的实现另一个是用户自己自己实现的)Synchronized的实现是jvm层面的很难看到其中的实现。而ReentrantLock是通过jvm实现的我们可以通过阅读jvm源码来查看实现。
  • 性能区别:在Synchronized优化之前Synchronized的性能相比ReentrantLock差很多,在Synchronized引入了偏向锁,轻量级锁也就是自旋锁之后了,两者的性能相差不大了。在两者都可用的情况下官方更推荐使用Synchronized,因为其写法更简单,Synchronized的优化就是借鉴了ReentrantLock中的cas技术。
  • 功能区别:便利性,很明显synchronized的使用更加便利,ReentrantLock在细粒度和灵活性中会优于Synchronized。

ReentrantLock独有功能

  • ReentrantLock可指定是公平锁还是非公平锁,Synchronized只能是非公平锁。(公平锁就是先等待的线程先获得锁)
  • ReentrantLock提供一个Condition类,可以分组唤醒需要唤醒的形成。synchronized是要嘛随机唤醒一个线程要嘛唤醒所有的线程。
  • ReentrantLock提供了一种能够中断等待锁的线程的机制lock.locInterruptibly(),ReentrantLock实现是一种自旋锁通过循环调用,通过cas机制来实现加锁。性能较好是因为避免了线程进入内核的阻塞状态
@Slf4j
public class LockExample2 {

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    public static int count = 0;

    private final static Lock lock = new ReentrantLock();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);
    }

    private static void add() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
}

我们首先使用 private final static Lock lock = new ReentrantLock()声明一个所得实例,然后使用

lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }

进行加锁和解锁操作。

我们在通过一个例子来看看这个ReentrantReadWriteLock怎么用。

@Slf4j
public class LockExample3 {

    private final Map<String, Data> map = new TreeMap<>();

    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    private final Lock readLock = lock.readLock();

    private final Lock writeLock = lock.writeLock();

    public Data get(String key) {
        readLock.lock();
        try {
            return map.get(key);
        } finally {
            readLock.unlock();
        }
    }

    public Set<String> getAllKeys() {
        readLock.lock();
        try {
            return map.keySet();
        } finally {
            readLock.unlock();
        }
    }

    public Data put(String key, Data value) {
        writeLock.lock();
        try {
            return map.put(key, value);
        } finally {
            readLock.unlock();
        }
    }

    class Data {

    }
}

通过 private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock()声明一个ReentrantReadWriteLock,然后再分别获取 private final Lock readLock = lock.readLock() private final Lock writeLock = lock.writeLock()读锁和写锁。

我们在这个map读的时候加上读锁在写的时候加上写锁,但是这里有问题就是这个锁是悲观锁,也就是说在执行写锁的时候一定不能有读锁,当读操作特 特别多的时候很有可能会让写锁一直无法执行。

我们看一下官方的例子学习一下,StampedLock

import java.util.concurrent.locks.StampedLock;

public class LockExample4 {

    class Point {
        private double x, y;
        private final StampedLock sl = new StampedLock();

        void move(double deltaX, double deltaY) { // an exclusively locked method
            long stamp = sl.writeLock();
            try {
                x += deltaX;
                y += deltaY;
            } finally {
                sl.unlockWrite(stamp);
            }
        }

        //下面看看乐观读锁案例
        double distanceFromOrigin() { // A read-only method
            long stamp = sl.tryOptimisticRead(); //获得一个乐观读锁
            double currentX = x, currentY = y;  //将两个字段读入本地局部变量
            if (!sl.validate(stamp)) { //检查发出乐观读锁后同时是否有其他写锁发生?
                stamp = sl.readLock();  //如果没有,我们再次获得一个读悲观锁
                try {
                    currentX = x; // 将两个字段读入本地局部变量
                    currentY = y; // 将两个字段读入本地局部变量
                } finally {
                    sl.unlockRead(stamp);
                }
            }
            return Math.sqrt(currentX * currentX + currentY * currentY);
        }

        //下面是悲观读锁案例
        void moveIfAtOrigin(double newX, double newY) { // upgrade
            // Could instead start with optimistic, not read mode
            long stamp = sl.readLock();
            try {
                while (x == 0.0 && y == 0.0) { //循环,检查当前状态是否符合
                    long ws = sl.tryConvertToWriteLock(stamp); //将读锁转为写锁
                    if (ws != 0L) { //这是确认转为写锁是否成功
                        stamp = ws; //如果成功 替换票据
                        x = newX; //进行状态改变
                        y = newY;  //进行状态改变
                        break;
                    } else { //如果不能成功转换为写锁
                        sl.unlockRead(stamp);  //我们显式释放读锁
                        stamp = sl.writeLock();  //显式直接进行写锁 然后再通过循环再试
                    }
                }
            } finally {
                sl.unlock(stamp); //释放读锁或写锁
            }
        }
    }
}

我们再将前面的里改成StampedLock

@Slf4j
public class LockExample5 {

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    public static int count = 0;

    private final static StampedLock lock = new StampedLock();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);
    }

    private static void add() {
        long stamp = lock.writeLock();
        try {
            count++;
        } finally {
            lock.unlock(stamp);
        }
    }
}

这里和之前的不一样的地方就是

long stamp = lock.writeLock();
        try {
            count++;
        } finally {
            lock.unlock(stamp);
        }

在加锁后会返回一个值,解锁的时候需要传入这个值。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Microsoft Windows程序设计

Microsoft Windows程序设计

佩措尔德 / 章立民 / 华中科技 / 2004-1 / 118.00元

Charles Petzold是全球最权威且知名的Windows程序设计专家,他将其最畅销Programming Microsoft Windows with C#一书加以改写,使之能完全适用于Visual Basic.NET的开发人员。这位畅销书的作家示范了如何使用Visual Basic.NET将Windows Forms的功能发挥到极致(Windows Forms是新一代的Windows程序......一起来看看 《Microsoft Windows程序设计》 这本书的介绍吧!

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具