JUC(java.util.concurrency)包中常用锁相关类

news/2024/5/20 6:11:32 标签: java, 面试, 开发语言, JUC

JUC之locks包下的常用锁相关类

  • java.util.concurrency.locks
  • Lock接口
    • synchronized、wait
    • Lock
    • 接口方法
    • 使用Lock
  • ReentrantLock重入锁
    • ReentrantLock与Synchronized的异同
    • ReentrantLock实现公平、非公平锁
    • 使用示例
  • ReadWriteLock重入读写锁
    • 接口方法
    • 使用ReadWriteLock
  • Condition信号量
    • 接口方法
    • 使用Condition
  • LockSupport锁当前线程
    • 接口方法
    • 使用LockSupport
  • 用锁原则

javautilconcurrencylocks_1">java.util.concurrency.locks

java.util.concurrency.locksjava.util.concurrency(JUC)包下,它是一个提供了锁机制相关的类包,比如:Lock, Condition, ReadWriteLock等。

Lock接口

在JDK5之前,Java应用程序对于多线程的并发安全处理只能基于synchronized关键字解决,但是synchronized在有些场景中会存在一些短板。比如:它并不适合于所有的并发场景。Lock的出现可以解决synchronized在某些场景中的短板,它比synchronized更加灵活。

synchronized、wait

synchronized可以加锁,wait、notify可以看做加锁和解锁。但是synchronized方式加锁存在一定问题,因此出现了显式锁Lock。

synchronized的问题:

java">1、同步块的阻塞无法中断,不能Interruptibly

2、同步块的阻塞无法控制超时,无法自动解锁

3、同步块无法异步处理锁,即不能立即知道是否可以拿到锁

4、同步块无法根据条件灵活的加锁解锁,即只能跟同步块范围一致

Lock

Lock锁采用接口设计,使用方式灵活可控,性能开销小,属于java.util.concurrent.locks锁工具包
在这里插入图片描述

接口方法

在这里插入图片描述

方法描述
void lock()获取锁,类似synchronized (lock)
void lockInterruptibly()获取锁,允许打断
Condition newCondition()新增一个绑定到当前Lock的条件
final Lock lock = new ReentrantLock()
final Condition notEmpty = lock.newCondition()
boolean tryLock()尝试无等待获取锁,成功则返回true
boolean tryLock(long time, TimeUnit unit)尝试获取锁,成功则返回true,超时则退出
void unlock()解锁,要求当前线程已获得锁,类似同步块结束

使用Lock

java">public class LockCounter {
    private int sum = 0;
    // 可重入锁+公平锁
    private Lock lock = new ReentrantLock(true);

    public int incrAndGet() {
        try {
            lock.lock();
            return ++sum;
        } finally {
            lock.unlock();
        }
    }

    public int getSum() {
        return sum;
    }

    public static void main(String[] args) {
        int loopNum = 10_0000;
        LockCounter counter = new LockCounter();
        // 从0到loopNum产生递增值序列,转成一个流,然后循环遍历0到loopNum数,进行求和
        IntStream.range(0, loopNum).parallel()
                .forEach(i -> counter.incrAndGet());
        System.out.println("counter.getSum() = " + counter.getSum());
    }
}
java">counter.getSum() = 100000

ReentrantLock重入锁

java">ReentrantLock是可以重入的锁,当一个线程获取锁时,还可以接着重复获取多次。

ReentrantLock具有与synchronized相同功能,但是会比synchronized更加灵活,具有更多的方法。

ReentrantLock底层基于AbstractQueuedSynchronizer实现。

ReentrantLock与Synchronized的异同

ReentrantLock和synchronized都是独占锁,只允许线程互斥的访问临界区。

实现上两者不同:

synchronized加锁解锁的过程是隐式的,用户不用手动操作,优点是操作简单,但不够灵活。一般并发场景使用synchronized

ReentrantLock需要手动加锁和解锁,且解锁的操作尽量要放在finally代码块中,保证线程正确释放锁。操作较为复杂,但是因为可以手动控制加锁和解锁过程,在复杂的并发场景中使用。

都是可重入:

synchronized因为可重入因此可以放在被递归执行的方法上,且不用担心线程最后能否正确释放锁

ReentrantLock在重入时要却确保重复获取锁的次数必须和重复释放锁的次数一样,否则可能导致其他线程无法获得该锁。

ReentrantLock实现公平、非公平锁

ReentrantLock可以实现公平、非公平锁。

公平锁是指当锁可用时,在锁上等待时间最长的线程将获得锁的使用权。非公平锁则随机分配这种使用权。

ReentrantLock和synchronized一样,默认ReentrantLock实现是非公平锁,因为相比公平锁,非公平锁性能更好。

创建公平锁

java">ReentrantLock lock = new ReentrantLock(true);

创建非公平锁

java">ReentrantLock lock = new ReentrantLock(false);

使用示例

java">public class ReentrantLockDemo {

    private static int count = 0;

    // 公平重入锁
    static Lock lock = new ReentrantLock(true);

    public static void inc() {
        // 线程获得锁(互斥锁)
        lock.lock();
        try {
            Thread.sleep(1);
            count++;
            getLock();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            //释放锁 
            lock.unlock();
        }
    }

    public static void getLock() {
        // 同一个线程再次来抢占锁 : 不需要再次抢占锁,而是只增加重入的次数
        lock.lock();
        try {
            count--;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> AtomicDemo.inc());
        t1.start();

        System.out.println("result:" + count);
    }
}
java">result:0

ReadWriteLock重入读写锁

ReadWriteLock管理一组锁:一个读锁,一个写锁。它适用于读多写少的并发情况。

读锁可以在没有写锁的时候被多个线程同时持有,写锁是独占的。因此,在读写锁的实现时必须确保写操作对读操作的影响。每次只能有一个写线程,但是同时可以有多个线程并发地读数据。

在这里插入图片描述

接口方法

java">Lock readLock(); 获取读锁,共享锁

Lock writeLock(); 获取写锁,独占锁,排斥读锁

使用ReadWriteLock

java">public class ReadWriteLockCounter {
    private int sum = 0;
    /**
     * 可重入、读写锁、公平/非公平锁
     * fair:true:公平锁 false:非公平锁
     * 公平锁意味排队靠前的优先
     * 非公平锁则都是同样机会
     */
    private ReadWriteLock lock = new ReentrantReadWriteLock(true);

    public int incrAndGet() {
        try {
            // 写锁 独占锁 被读锁排斥
            lock.writeLock().lock();
            return ++sum;
        } finally {
            lock.writeLock().unlock();
        }
    }

    public int getSum() {
        try {
            // 读锁 共享锁 保证可见性
            lock.readLock().lock();
            return sum;
        } finally {
            lock.readLock().unlock();
        }
    }

    public static void main(String[] args) {
        int loopNum = 10_0000;
        ReadWriteLockCounter readWriteLockCounter = new ReadWriteLockCounter();
        // 从0到loopNum产生递增值序列,转成一个流,然后循环遍历0到loopNum数,进行求和
        IntStream.range(0, loopNum).parallel()
                .forEach(i -> readWriteLockCounter.incrAndGet());
        System.out.println("counter.getSum() = " + readWriteLockCounter.getSum());
        System.out.println("counter.getSum() = " + readWriteLockCounter.getSum());
    }
}
java">counter.getSum() = 100000
counter.getSum() = 100000

Condition信号量

Condition是一个多线程协调通信的工具类,可以让某些线程一起等待某个条件( condition ),只有满足条件时,线程才会被唤醒

Condition具有线程的wait和notify的功能,有2个核心方法:

java">await:把当前线程阻塞挂起

signal:唤醒阻塞的线程

当调用await方法后,当前线程会释放锁并等待,而其他线程调用condition对象的signal或者signalall方法通知并被阻塞的线程,然后自己执行unlock释放锁,被唤醒的线程获得之前的锁继续执行,最后释放锁。

接口方法

在这里插入图片描述

方法描述
void await()等待信号,相当于Object的wait()
boolean await(long time, TimeUnit unit)等待信号,超时则返回false
long awaitNanos(long nanosTimeout)等待信号,直到被信号发送或中断, 或指定等待时间过去
void awaitUninterruptibly()等待信号
boolean awaitUntil(Date deadline)等待信号, 超时则返回false
void signal()给一个等待线程发送唤醒信号, 相当于Object的notify ()
void signalAll()给所有等待线程发送唤醒信号,相当于Object的notifyAll()

使用Condition

java">    public static void main(String[] args) {
        Queue<String> queue = new LinkedList<>();
        // 重入锁
        Lock lock = new ReentrantLock();
        // 通过Lock.newCondition()创建,可以看做是Lock对象上的信号,类似于wait/notify
        Condition condition = lock.newCondition();
        int maxSize = 5;

        Producer producer = new Producer(queue, maxSize, lock, condition);
        Consumer consumer = new Consumer(queue, maxSize, lock, condition);

        Thread t1 = new Thread(producer);
        Thread t2 = new Thread(consumer);
        t1.start();
        t2.start();
    }
}
java">public class Producer implements Runnable {

    private Queue<String> msg;

    private int maxSize;

    Lock lock;
    Condition condition;

    public Producer(Queue<String> msg, int maxSize, Lock lock, Condition condition) {
        this.msg = msg;
        this.maxSize = maxSize;
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        int i = 0;
        while (true) {
            i++;
            lock.lock();
            while (msg.size() == maxSize) {
                System.out.println("生产者队列满了,先等待");
                try {
                    condition.await(); // 阻塞线程并释放锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("生产消息:" + i);
            msg.add("生产者的消息内容" + i);
            condition.signal(); // 唤醒阻塞状态下的线程
            lock.unlock();
        }
    }
}
java">public class Consumer implements Runnable {
    private Queue<String> msg;

    private int maxSize;

    Lock lock;
    Condition condition;

    public Consumer(Queue<String> msg, int maxSize, Lock lock, Condition condition) {
        this.msg = msg;
        this.maxSize = maxSize;
        this.lock = lock;
        this.condition = condition;
    }

    @Override
    public void run() {
        int i = 0;
        while (true) {
            i++;
            lock.lock(); //synchronized
            while (msg.isEmpty()) {
                System.out.println("消费者队列空了,先等待");
                try {
                    condition.await(); //阻塞线程并释放锁   wait
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("消费消息:" + msg.remove());
            condition.signal(); //唤醒阻塞状态下的线程
            lock.unlock();
        }
    }
}

java">生产消息:1
生产消息:2
生产消息:3
生产消息:4
生产消息:5
生产者队列满了,先等待
消费消息:生产者的消息内容1
消费消息:生产者的消息内容2
消费消息:生产者的消息内容3
消费消息:生产者的消息内容4
消费消息:生产者的消息内容5
消费者队列空了,先等待
生产消息:6
生产消息:7
生产消息:8
生产消息:9

LockSupport锁当前线程

LockSupport类似于Thread 类的静态方法,专门处理执行代码的本线程。

接口方法

在这里插入图片描述

方法描述
void park(Object blocker)暂停当前线程
void parkNanos(Object blocker,long nanos)暂停当前线程,不过有超时时间的限制
void parkUntil(Object blocker,long deadline)暂停当前线程,直到某个时间
void park()无期限暂停当前线程
void parkNanos(long nanos)暂停当前线程,不过有超时时间的限制
void parkUntil(long deadline)暂停当前线程,直到某个时间
void unpark(Thread thread)恢复当前线程。传入Thread参数,是因为一个park线程,无法自己唤醒自己,所以需要其他线程来唤醒
Object getBlocker(Thread t)

使用LockSupport

java">
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 开始执行");
            LockSupport.park();
            System.out.println(Thread.currentThread().getName() + " 被唤醒");
        }, "t1");
        t1.start();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + " 开始执行");
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println(Thread.currentThread().getName() + " 开始唤醒");
            LockSupport.unpark(t1);
        }, "t2").start();
    }
java">t1 开始执行
t2 开始执行
t2 开始唤醒
t1 被唤醒

用锁原则

java">1. 只在更新对象的成员变量时加锁

2. 只在访问可变的成员变量时加锁

3. 不在调用其他对象的方法时加锁

4. 降低锁范围:锁定代码的范围、作用域

5. 细分锁粒度:一个大锁,拆分成多个小锁

http://www.niftyadmin.cn/n/2164.html

相关文章

【组原课设团队任务】FlyBird+FPGA+RISCV

小兔子乖乖队&#xff1a;使用FPGA设计与实现Flybird B站地址https://www.bilibili.com/video/BV1Km4y1c78B/?vd_sourced3e5165825082cd17457aab2378b8f54 项目整体设计 经过个人任务的实现&#xff0c;本队拥有4个logisim实现的CPU以及1个FPGA实现的CPU&#xff0c;logisim工…

【数据结构】动态顺序表(C语言实现)

文章目录0. 前言1. 线性表2. 顺序表2.1 概念及结构3. 动态顺序表的实现3.1 定义结构3.2 接口函数总览3.3 初始化3.4 检查增容3.5 尾插3.6 尾删3.7 头插3.8 头删3.9 查找3.10 指定下标位置插入3.11 指定下标位置删除3.12 修改3.13 打印3.14 销毁4. 完整代码SeqList.hSeqList.cte…

6 8051使用Keil模拟器和调试输出窗口实现串口收发回环

本仓库相关网址&#xff1a; CSDN文章地址 Gitee工程和源码地址 相关仓库&#xff1a; 嵌入式整体介绍&#xff0c;里面也描述了部分8051的内容&#xff1a; 才鲸嵌入式 / 嵌入式知识图谱WiKi C语言框架讲解&#xff0c;让你对C语言要学哪些东西有一个完整的了解&#xff1a; …

java计算机毕业设计家政服务网站源程序+mysql+系统+lw文档+远程调试

java计算机毕业设计家政服务网站源程序mysql系统lw文档远程调试 java计算机毕业设计家政服务网站源程序mysql系统lw文档远程调试本源码技术栈&#xff1a; 项目架构&#xff1a;B/S架构 开发语言&#xff1a;Java语言 开发软件&#xff1a;idea eclipse 前端技术&#xff1…

高尔夫模拟器,极致的室内高尔夫球运动体验!

说到高尔夫的时候&#xff0c;人们脑海中很自然的浮现出的画面就是和煦的微风、温暖的阳光、绿油油的草坪绿地、运动服……&#xff0c;近百年来高尔夫运动在世界各地广受欢迎&#xff0c;已经成为世界前几的热门球类运动项目之一&#xff0c;伴随着高尔夫球文化的传播、赛事的…

公众号查题使用接口搭建

公众号查题使用接口搭建 本平台优点&#xff1a; 多题库查题、独立后台、响应速度快、全网平台可查、功能最全&#xff01; 1.想要给自己的公众号获得查题接口&#xff0c;只需要两步&#xff01; 2.题库&#xff1a; 查题校园题库&#xff1a;查题校园题库后台&#xff08;…

C++模板详解--函数模板及类模板

目录 泛型编程 函数模板 函数模板概念 函数模板的格式 函数模板的原理 函数模板的实例化 1.隐式实例化 2.显示实例化 函数模板的匹配原则 类模板 类模板的定义格式 类模板的实例化 泛型编程 在进入模板之前&#xff0c;我们先来了解一下泛型编程&#xff0c;如果要…

亚马逊 sp-api更新库存 feed 方式,xsd 验证xml

更新库存遇到的坑&#xff0c; xml格式不对&#xff0c;因为输出的测试xml &#xff0c;后面又加了点字符串&#xff0c;因为这个错误&#xff0c;耽误我好几天。。。下次测试结果时候&#xff0c;一定要等变量完全停止定义的后面 输出 if($debug) echo htmlentities($xml);$f…