19.Lock锁介绍与应用案例

在JUC中,Lock是一个接口,其作用与synchronized类似,都是为了保证线程安全而提前加锁,不同的是Lock只定义了与抢锁和释放锁相关的操作,没有具体实现,而且要用lock()和unlock()将要保护的代码给包起来才可以。其中常用的接口有三个:

  • lock():抢占锁资源方法,如果当前线程没有抢占到锁,则阻塞。

  • trylock():尝试抢占锁资源,如果抢占成功则返回true,否则返回false。

  • unlock():释放锁。

Lock既然是一个接口,那么必须有具体的实现才可以,常见的有三个:

  • 重入锁ReentrantLock

  • 读写锁ReentrantReadWriteLock

  • 读写锁的改进版StampedLock

1 重入锁ReentrantLock

ReentrantLock是一个支持重入的排他锁。所谓排他就是同一时刻只允许一个线程获得锁。

那重入是什么意思呢?意思是如果一个线程获得了锁,那么后面再访问相同的资源时,不需要再加锁就可以访问,一般此时会记录重入次数的。如果还是不清楚,我们看一个例子:

void update(a){
    insert(a);
}

这是我们常见的一种写法,在update某个记录的时候,我们可能在其内部要先做一个插入操作,一般来说,update和insert都需要先对资源进行加锁。如果操作的是同一个资源,比如这里的update对a加锁了,那执行insert时一定还没有释放,那insert该如何获得锁呢?

此时最好的方式就是让a在执行insert时可以直接获得锁资源,完成之后updat再继续执行,这就是重入的意思,synchronized和ReetrantLock都支持重入。

重入锁的例子:

public class ReentrantLockExample {
    static Lock lock=new ReentrantLock();
    private int count = 0;
    public void incr(){
        lock.lock();
        try {
            count++;
        }finally {
            lock.unlock();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ReentrantLockExample atomicExample = new ReentrantLockExample();
        Thread[] threads=new Thread[2];
        for (int j = 0;j<2;j++) {
            threads[j]=new Thread(() -> {
                for (int k=0;k<10000;k++) {
                    atomicExample.incr();
                }
            });
            threads[j].start();
        }
        threads[0].join();//保证线程执行结束
        threads[1].join();
        System.out.println(atomicExample.count);
    }
}

上面的例子中,针对count++进行了加锁,从而保证count++在多线程访问下的线程安全性。

2 读写锁ReentrantReadWriteLock

ReentrantReadWriteLock表示可以重入的读写锁,那什么是读写锁呢?想象一下,开会的时候领导在画大饼,大家可以同时看,如果两个领导同时画,我们该怎么看呢?这就是读写的实际价值。读的时候可以多个人同时读,而写的时候只能一个人写。另外为了防止写的过程中,读到的信息可能前后不一致,因此一般写的时候也不能读,具体来说读写锁的特征就是:

  • 读/读不互斥,多个线程访问,线程不会阻塞。

  • 读/写互斥,如果一个读一个写,此时就要阻塞一个。

  • ReentrantReadWriteLock写/写互斥,多个线程同时写,要互斥

看个例子:

public class ReadWriteLockExample1 {
    private final Lock lock=new ReentrantLock();
    private List<String> dataList=new ArrayList<>();
    public void add(String data)  {
        lock.lock();
        try {
            dataList.add(data);
        }finally {
             lock.unlock();
        }
    }
    
    public String get(int idx){
        lock.lock();
        try{
            return dataList.get(idx);
        }finally {
            lock.unlock();
        }
    }
   } 

这个例子中,dataList不是线程安全的,为此采用重入加锁机制,但是这里的get()和add()是同一级别的操作。如果get()操作很多,那么add()就会被阻塞。不过get方法本身不会影响到数据,因此对get的加锁是不必要的,因此我们需要一个更合理的加锁方法,这就是读写锁。

基于读写锁的代码如下:

public class ReadWriteLockExample {
    private final ReadWriteLock readWriteLock=new ReentrantReadWriteLock();
    private final Lock readLock=readWriteLock.readLock();
    private final Lock writeLock=readWriteLock.writeLock();
    private List<String> dataList=new ArrayList<>();
    public void add(String data)  {
        writeLock.lock();
        try {
            dataList.add(data);
        }finally {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            writeLock.unlock();
        }
    }
    public String get(int idx){
        readLock.lock();
        try{
            return dataList.get(idx);
        }finally {
            readLock.unlock();
        }
    }
}

上面代码中,通过读锁readLock和writeLock把读写的操作做了分离,从而减少读锁带来的竞争,其具体实现原理,我们后面章节再看。

3 StampedLock

ReentrantReadWriteLock有个问题,就是如果有get()操作,那么所有的add()必须等待,也就是读的过程中是不允许写的,而如果get()过多,就会导致写线程一直被阻塞。为了解决这个问题,Jdk8引入了StampedLock锁,对读写访问进行了优化,其基本原理是提供了一种乐观的策略,当有线程调用get()方法读取数据时,不会阻塞准备执行写操作的过程,具体使用方法如下:

public class StampedLockExample {
    private int balance;
    private StampedLock lock = new StampedLock();
    public StampedLockExample() {
        balance=10;
    }

    public void conditionReadWrite (int value) {
        // 首先判断balance的值是否符合更新的条件
        long stamp = lock.readLock();
        while (balance > 0) {
            long writeStamp = lock.tryConvertToWriteLock(stamp);
            if(writeStamp != 0) { // 成功转换成为写锁
                stamp = writeStamp;
                balance += value;
                break;
            } else {
                // 没有转换成写锁,这里需要首先释放读锁,然后再拿到写锁
                lock.unlockRead(stamp);
                // 获取写锁
                stamp = lock.writeLock();
            }
        }
        lock.unlock(stamp);
    }

    public void optimisticRead() {
        long stamp = lock.tryOptimisticRead();
        int c = balance;
        // 这里可能会出现了写操作,因此要进行判断
        if(!lock.validate(stamp)) {
            // 要重新读取
            stamp = lock.readLock();
            try{
                c = balance;
            }
            finally{
                lock.unlockRead(stamp);
            }
        }
        System.out.println("读取的值为:"+c);
    }

    public void read () {
        long stamp = lock.readLock();
        lock.tryOptimisticRead();
        int c = balance;
        System.out.println("读取的值为:"+c);
        // ...
        lock.unlockRead(stamp);
    }

    public void write(int value) {
        long stamp = lock.writeLock();
        balance += value;
        lock.unlockWrite(stamp);
    }
    public static void main(String[] args) {
        StampedLockExample demo=new StampedLockExample();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    demo.read();
                    demo.optimisticRead();
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                while(true){
                    demo.write(2);
                    demo.conditionReadWrite(3);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

纵横千里,捭阖四方

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值