ReentrantLock、StampedLock、ReentrantReadWriteLock、Condition

admin
2022-04-26 / 0 评论 / 165 阅读 / 正在检测是否收录...
温馨提示:
本文最后更新于2022年04月25日,已超过811天没有更新,若内容或图片失效,请留言反馈。

锁分为Synchronized和锁Lock。

ReentrantLock与锁

ReentrantLock(可重入锁)与Synchronized(同步锁)的区别:

  1. 可重入性:区别不大,都是通过计数器,当计数器下降为0时,锁释放。
  2. 锁的实现:Synchronized依赖JVM,ReentrantLock基于JDK实现。(类似Synchronized是操作系统实现,ReentrantLock是敲代码实现),ReentrantLock可通过看源码明白实现,Synchronized依赖JVM就看不到实现原理。
  3. 性能的区别:优化之前Synchronized比ReentrantLock性能差很多,优化后Synchronized引入偏向锁、轻量级锁也就是自旋锁后,2者性能差不多了。差不多时,官方推荐Synchronized。
  4. 功能的区别:Synchronized的使用方便简洁,隐式获取锁释放锁,ReentrantLock需要手动获取锁,释放锁,如果忘记容易出现死锁,所以最好再finally中释放锁。锁的颗粒度和灵活度很明显ReentrantLock优于Synchronized。

    ReentrantLock独有的功能:

    1. ReentrantLock可指定公平锁、非公平锁,而Synchronized只能时非公平锁。公平锁:先等待的先获取锁,反之亦然。
    2. 提供了一个Condition类,可以分组唤醒需要唤醒的线程,Synchronized要么随机唤醒,要么全部唤醒。
    3. 提供能够中断等待锁的线程的机制,lock.lockInterruptibly(),lockInterruptibly是一种自旋锁,通过循环来调用CAS来加锁,性能比较好,就是因为不会进入内核阻塞状态。

    使用场景:必须实现上面3个功能时,就必须用了。

代码示例一

ReentrantLock实现累加,注意与synchronized的不用用法

package com.yanxizhu.demo.concurrency.lock;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @description: ReentrantLock锁实现计数,注意使用synchroized怎么实现的
 * @author: <a href="mailto:batis@foxmail.com">清风</a>
 * @date: 2022/4/25 23:09
 * @version: 1.0
 */
@Slf4j
public class ReentrantLockDemo {
    //定义锁
    private static Lock lock = new ReentrantLock();
    //定义锁用户数
    private static final int totalThread = 5000;
    //定义并发线程数
    private static final int concurrentThread = 200;

    //定义总和
    private static int count = 0;

    public static void main(String[] args) throws Exception{
        //定义线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //定义信号量
        final Semaphore semaphore = new Semaphore(concurrentThread);
        //定义闭锁
        final CountDownLatch countDownLatch = new CountDownLatch(totalThread);

        for (int i = 0; i < totalThread; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    sum();
                    semaphore.release();
                } catch (InterruptedException e) {
                    log.error("错误信息:【{}】", e.getMessage());
                }
                countDownLatch.countDown();
            });
        }

        countDownLatch.await();
        executorService.shutdown();
        log.info("最后累加结果Count={}", count);
    }

    //通过ReentrantLock锁实现
    public static void sum() {
        lock.lock();
        count++;
        lock.unlock();
    }

    //累加求和(synchronized锁实现)
//    public synchronized static void sum() {
//        count ++;
//    }
    
}

输出结果:

23:21:08.549 [main] INFO com.yanxizhu.demo.concurrency.lock.ReentrantLockDemo - 最后累加结果Count=5000

源码查看

public ReentrantLock() {
        sync = new NonfairSync();
 }

默认传入的是不公平的锁。

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

可以传入ture或false决定是否是公平锁。

需要注意的方法tryLok:

    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }

本质是仅在调用的时候,锁定未被另一个线程持有的情况下,才锁定。参数同样可以传入超时时间和单位,保持线程时间多久可以锁定。提供了丰富的方法,可以参考官方文档,

ReentrantReadWriteLock

没有任何读写锁的时候,才可以取得写入锁。可实现悲观读取。读多写少时,可能会导致写进入饥饿。注意是悲观锁,当有读时,写可能一直执行不了,有写的时候,不能读。

代码示例

package com.yanxizhu.demo.concurrency.lock;

import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @description: ReentrantReadWriteLock 读锁、写锁的使用
 * 注意: ReentrantReadWriteLock时悲观锁,读的时候不能写,写的时候不能读,互斥的
 * @author: <a href="mailto:batis@foxmail.com">清风</a>
 * @date: 2022/4/25 23:40
 * @version: 1.0
 */
public class ReentrantReadWriteLockDemo {
    //定义读写锁
    private static final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    //获取读锁
    private static final Lock readLock = lock.readLock();
    //获取写锁
    private static final Lock writeLock = lock.writeLock();

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

    public class Data{

    }

    //读map中的数据
    public Data getValue(String key) {
        readLock.lock();
        try{
            return map.get(key);
        }catch (Exception e) {

        }finally {
            readLock.unlock();
        }
        return null;
    }

    //读取map中所有的key
    public Set<String> getKeys() {
        readLock.lock();
        try{
            return map.keySet();
        }catch (Exception e) {

        }finally {
            readLock.unlock();
        }
        return null;
    }

    //向map中写入数据
    public void writeDate(String key, Data value) {
        writeLock.lock();
        try{
            map.put(key, value);
        }catch (Exception e){

        }finally {
            writeLock.unlock();
        }
    }
}

StampedLock

StampedLock控制锁,有3种模式。

写、读、

是由锁和版本控制,就是这里的。

读分为悲观读和乐观读。

官方代码示例

package com.yanxizhu.demo.concurrency.lock;

import java.util.concurrent.locks.StampedLock;

public class StampedLockDemo {

    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); //释放读锁或写锁
            }
        }
    }
}

示例代码二

累加求和

package com.yanxizhu.demo.concurrency.lock;

import lombok.extern.slf4j.Slf4j;

import javax.annotation.concurrent.ThreadSafe;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.StampedLock;

@Slf4j
@ThreadSafe
public class StampedLockDemo2 {

    // 请求总数
    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);
        }
    }
}

输出结果:5000,线程安全,并发性好。

Condition

代码示例

package com.yanxizhu.demo.concurrency.lock;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * ReentrantLock中另一个队列,Condition的使用
 */
@Slf4j
public class ConditionDemo {

    public static void main(String[] args) {
        ReentrantLock reentrantLock = new ReentrantLock();
        Condition condition = reentrantLock.newCondition();

        new Thread(() -> {
            try {
                reentrantLock.lock();
                log.info("wait signal"); // 1
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("get signal"); // 4
            reentrantLock.unlock();
        }).start();

        new Thread(() -> {
            reentrantLock.lock();
            log.info("get lock"); // 2
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            condition.signalAll();
            log.info("send signal ~ "); // 3
            reentrantLock.unlock();
        }).start();
    }
}
4

评论 (0)

取消