前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【JUC基础】04. Lock锁

【JUC基础】04. Lock锁

作者头像
有一只柴犬
发布2024-01-25 11:00:53
580
发布2024-01-25 11:00:53
举报
文章被收录于专栏:JAVA体系JAVA体系

1、前言

java.util.concurrent.locks为锁定和等待条件提供一个框架的接口和类,说白了就是锁所在的包。

2、什么是Lock

Lock是一种锁机制,比同步块(synchronized block)更加灵活,同时也更加复杂的线程同步机制。在JDK1.5就已存在Lock接口了。

其中有三个实现类:

  1. ReentrantLock:可重入锁
  2. ReentrantReadWriteLock.ReadLock:读锁
  3. ReentrantReadWriteLock.WriteLock:写锁

3、Lock的API

Lock接口只提供了6个方法:

其中lock()和unlock()是配对的。lock()是加锁,而unlock()是解锁。lockInterruptibly()与lock()类似,区别在于lock()如果无法获取到锁,线程一直被阻塞,直到锁释放。而lockInterruptibly()允许线程被中断,并抛出java.lang.InterruptedException。

tryLock()只有在调用时才可以获得锁。如果可用,则获取锁定,并返回true,反之返回false。相应的还有重载方法tryLock(long time, TimeUnit unit)表示在给定的等待时间内空闲,则可以获取锁。如果到了超时时间,还没获取到就放弃获取。

4、ReentrantLock的基本使用

Lock是一个接口,官方文档其实也给了如何使用的说明:

  1. 声明Lock对象。Lock lock = new XXXLock();
  2. 方法执行加锁lock.lock();
  3. 在方法块执行完毕后,需要释放锁:lock.unlock();

由于LocK是一个接口,需要使用具体的实现。典型的实现如ReentrantLock。示例代码如下:

代码语言:javascript
复制
public class ReentrantLockDemo {

    public static void main(String[] args) {
        Phones phones = new Phones();

        new Thread(() -> {
            for(int i = 0; i< 50; i++) {
                phones.sale();
            }
        }, "销售员小王").start();

        new Thread(() -> {
            for(int i = 0; i< 50; i++) {
                phones.sale();
            }
        }, "销售员小红").start();
    }

}

class Phones {
    // 库存10部手机
    private int total = 50;

    // 1、 声明锁的实例
    ReentrantLock lock = new ReentrantLock();

    public void sale(){
        try {
            // 2、加锁,代码规约检测会提示你加载try的第一行
            lock.lock();
            if(total > 0){
                System.out.println(Thread.currentThread().getName() + "卖出了一部手机,当前库存剩余:" + (--total));
            }
        } finally {
            // 3、释放锁
            lock.unlock();
        }
    }
}

执行结果:

ReentrantLock是个可重入锁,其中可以执行公平锁和非公平锁。如new ReentrantLock(true),默认是非公平锁(false)。

4.1、公平锁和非公平锁

  • 公平锁:每个线程获取锁的顺序按照先后顺序获取。关键字眼:先到先得。
    • 优点:能保证所有的线程都得到资源,不会产生线程饥饿现象。
    • 缺点:吞吐量低,除了第一个线程以外,其余的都处于排队阻塞的状态,cpu需要每次唤醒线程,开销较大。
  • 非公平锁:多个线程同时尝试获取,哪个线程优先获取到锁取决于系统分配策略。关键字眼:无需排队。
    • 优点:吞吐量高,cpu无需唤醒所有的线程,开销低。
    • 缺点:会产生线程饥饿现象,可能后到的线程先获取到锁,而前面的线程永远都获取不到

5、读写锁ReadWriteLock

ReadWriteLock是一个读写锁接口。什么是读写锁呢?看下官方文档说明:

ReadWriteLock分为一个读锁和一个写锁。读锁可以被多个线程持有,而写锁只能被一个线程持有。典型的实现类有ReentrantReadWriteLock。

读锁:就是我们常说的共享锁。

写锁:就是常说的独占锁。

示例代码:

代码语言:javascript
复制
public class ReadWriteLock {

    public static void main(String[] args) {
        MyMap map = new MyMap();

        // 写操作
        for (int i = 0; i < 5; i++) {
            int finalI = i;
            new Thread(() -> map.put(String.valueOf(finalI), String.valueOf(finalI))).start();
        }

        // 读操作
        for (int i = 0; i < 5; i++) {
            int finalI = i;
            new Thread(() -> map.get(String.valueOf(finalI))).start();
        }
    }

}

// 模拟公共资源类
class MyMap extends HashMap<String, String> {

    @Override
    public String get(Object key) {
        System.out.println(Thread.currentThread().getName() + "获取key:" + key);
        return super.get(key);
    }

    @Override
    public String put(String key, String value) {
        System.out.println(Thread.currentThread().getName() + "写入key:" + key);
        String put = super.put(key, value);
        System.out.println(Thread.currentThread().getName() + "写入key:" + key + "完成");
        return put;
    }
}

如果不加入任何的锁限制,我们直到结果肯定是很随机的。在写入操作时会被其他读线程插队。

加入读写锁后:

代码语言:javascript
复制
public class ReadWriteLock {

    public static void main(String[] args) {
        MyMap map = new MyMap();

        // 写操作
        for (int i = 0; i < 5; i++) {
            int finalI = i;
            new Thread(() -> map.put(String.valueOf(finalI), String.valueOf(finalI))).start();
        }

        // 读操作
        for (int i = 0; i < 5; i++) {
            int finalI = i;
            new Thread(() -> map.get(String.valueOf(finalI))).start();
        }
    }

}

// 模拟公共资源类
class MyMap extends HashMap<String, String> {

    private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();

    @Override
    public String get(Object key) {
        try{
            rwLock.readLock().lock();

            System.out.println(Thread.currentThread().getName() + "获取key:" + key);
            return super.get(key);

        } finally {
            rwLock.readLock().unlock();
        }
    }

    @Override
    public String put(String key, String value) {
        try {
            rwLock.writeLock().lock();

            System.out.println(Thread.currentThread().getName() + "写入key:" + key);
            String put = super.put(key, value);
            System.out.println(Thread.currentThread().getName() + "写入key:" + key + "完成");
            return put;
        } finally {
            rwLock.writeLock().unlock();
        }
    }
}

我们可以看到在写入的时候,并不会有读的线程插队操作。

6、小结

关于Lock锁大概就讲这些,主要讲了ReentrantLock和ReadWriteLock的基本使用,也是通常比较常用的。其中Locks中还有一个接口Condition,这个等后面讲生产者和消费者的时候在细说。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2024-01-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、前言
  • 2、什么是Lock
  • 3、Lock的API
  • 4、ReentrantLock的基本使用
  • 4.1、公平锁和非公平锁
  • 5、读写锁ReadWriteLock
  • 6、小结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档