java ReentrantLock 锁 await、signal的用法

这篇具有很好参考价值的文章主要介绍了java ReentrantLock 锁 await、signal的用法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

背景

在并发编程中,为了保证线程的原子执行,需要使用锁,jvm 内 可以使用 synchronized 和 ReentrantLock,如果是集群部署,我们可以使用Redis 分布式锁 其他的锁后面再介绍。

ReentrantLock 和 synchronized

1、ReentrantLock 通过方法 lock()与 unlock()来进行加锁与解锁操作,与synchronized(1.8之后性能得到提升)会被JVM自动解锁机制不同,ReentrantLock 加锁后需要手动进行解锁

2、ReentrantLock 相比 synchronized 的优势是可中断、公平锁、多个锁

简述

ReentrantLock 继承接口 Lock 并实现了接口中定义的方法,他是一种可重入锁,除了能完
成 synchronized 所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等
避免多线程死锁的方法

Lock 在 java.util.concurrent.locks

ReentrantLock 实现了 Lock 接口,拥有下面几个方法:

package java.util.concurrent.locks;
import java.util.concurrent.TimeUnit;

public interface Lock {

    //执行此方法时, 如果锁处于空闲状态, 当前线程将获取到锁. 相反, 如果锁已经
被其他线程持有, 将禁用当前线程, 直到当前线程获取到锁
    void lock();

    //如果当前线程未被中断,获取锁
    void lockInterruptibly() throws InterruptedException;

    //如果锁可用, 则获取锁, 并立即返回 true, 否则返回 false
    boolean tryLock();

   
    //如果锁在给定等待时间内没有被另一个线程保持,则获取该锁并返回 true,否则返回 false。
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

   //执行此方法时, 当前线程将释放持有的锁. 锁只能由持有者释放, 如果线程
并不持有锁, 却执行该方法, 可能导致异常的发生
    void unlock();

    //条件对象,获取等待通知组件
    Condition newCondition();
}

初始化 锁

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

public class LockTest {

    Lock lock = new ReentrantLock();

    Condition condition = lock.newCondition();
}

说明

java ReentrantLock 锁 await、signal的用法,高并发,java,java,开发语言

1、同一时刻只有一个线程能获取独占锁(lock.lock()),持有了这个锁的人才能调用condition.await()和 condition.sign()方法。


2、调用 condition.await()方法会释放独占锁(lock.unlock),并且将自身加入到condition队列中。


3、Thread-signal-1线程获取lock 后,condition.signal()方法不会立即唤醒await中的线程,而是将condition队列中的线程转移到AQS队列中。


4、当持锁线程释放锁时,AQS队列的线程再抢到则会被唤醒。

线程池中使用锁

1、我们使用线程池 来演示 如何使用 ReentrantLock,首先创建一个 固定的线程池

ExecutorService executor = Executors.newFixedThreadPool(10);

2、发起10个线程的并发处理,这里使用for 循环来提交 线程到线程池

3、每个线程 实现 Runnable 接口的run 方法

4、run方法中尝试获取锁,等待2秒,获取到锁后,休眠10秒,然后调用await 方法,在调用await方法时,当前线程会把自身加入到condition中,同时释放 lock 锁(当然这里也有部分线程获取不到锁)。

5、run 方法执行完成后,本次请求结束。

6、请求unlock路由,这时线程会去尝试获取 lock , 然后 调用 condition.signal() 方法 来唤起刚刚通过 condition.await() 加入到condition 队列中的线程,当lock.unlock() 后,会执行刚刚加入condition队列中线程的剩余代码并打印 "signal-> await 执行剩余的代码:pool-4-thread-1"。

package com.yd.controller.user;

import com.yd.entity.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@RestController
@RequestMapping("/admin/user")
public class LockTest {

    Lock lock = new ReentrantLock();

    Condition condition = lock.newCondition();

    @GetMapping("exec")
    public void execs() {
        ExecutorService executor = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        if (lock.tryLock(2, TimeUnit.SECONDS)) {
                            System.out.println("获取到锁:" + Thread.currentThread().getName());
                            TimeUnit.SECONDS.sleep(10);
                            condition.await(); //此处会将当前线程放到队列condition,并释放lock锁,其余线程才能获取到锁,但下面的代码不会被执行,只有当signal() 方法被调用时,才会打印下面的代码
                            System.out.println("signal-> await 执行剩余的代码:" + Thread.currentThread().getName());
                            lock.unlock();
                        } else {
                            System.out.println("未获取锁" + Thread.currentThread().getName());
                        }
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
        executor.shutdown();
    }

    @GetMapping("unlock")
    public ResponseEntity<String>  unlock() {

        lock.lock();
        condition.signalAll();
        lock.unlock();
        return ResponseEntity.success("yes");
    }
}

启动springboot 后,访问

curl -X GET http://127.0.0.1:8089/admin/user/exec

执行结果如下,只有 thread-1 获取到锁,其余线程均未获取到锁

获取到锁:pool-4-thread-1
未获取锁pool-4-thread-2
未获取锁pool-4-thread-7
未获取锁pool-4-thread-4
未获取锁pool-4-thread-5
未获取锁pool-4-thread-6
未获取锁pool-4-thread-10
未获取锁pool-4-thread-3
未获取锁pool-4-thread-8
未获取锁pool-4-thread-9

接下来访问

curl http://127.0.0.1:8089/admin/user/unlock

执行结果如下,会将上面 thread-1 加入到condition 队列中的剩余代码执行完毕。

signal-> await 执行剩余的代码:pool-4-thread-1

注意事项

在单元测试中,想要看到锁的效果,需要在代码后面加一个 睡眠提醒(Thread.sleep(10000),主线程结束后,锁的运行逻辑表现不出来。

在springboot中,主线程默认是后台运行的,没有影响。

Semaphore 信号量

       

Semaphore 是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信
号,做完自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。Semaphore 可以用来
构建一些对象池,资源池之类的,比如数据库连接池。

Semaphore 基本能完成 ReentrantLock 的所有工作,使用方法也与之类似,通过 acquire()与
release()方法来获得和释放临界资源。

在springboot中使用单元测试,直接上代码:

 @Test
    public void testSem() throws InterruptedException {
        Semaphore semaphore = new Semaphore(5);
        ExecutorService executor = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        semaphore.acquire();
                        Thread.sleep(4000);
                        //DateFormatUtils 使用时,需要引入common-lang3 包
                        System.out.println(DateFormatUtils.format(new Date(), "yyyy-MM-dd HH:mm:ss") + "获取到锁");
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    } finally {
                        semaphore.release();
                    }
                }
            });
        }
        executor.shutdown();
        //让主线程挂起,否则程序直接退出
        Thread.sleep(20000);
    }

执行结果如下, 开启10个线程来执行,每次最多5个线程获取到锁

2023-08-29 19:50:52获取到锁
2023-08-29 19:50:52获取到锁
2023-08-29 19:50:52获取到锁
2023-08-29 19:50:52获取到锁
2023-08-29 19:50:52获取到锁


2023-08-29 19:50:56获取到锁
2023-08-29 19:50:56获取到锁
2023-08-29 19:50:56获取到锁
2023-08-29 19:50:56获取到锁
2023-08-29 19:50:56获取到锁文章来源地址https://www.toymoban.com/news/detail-681701.html

到了这里,关于java ReentrantLock 锁 await、signal的用法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Signal tap 的各种用法

      本文分为两部分,前一部分用于介绍signal tap基本功能,如果是初学者,看完这部分就可以用signal tap抓取一些简单的波形数据了。第二部分用于介绍一些特殊要求时的软件设置,比如连续触发,自定义触发条件等等。Signal Tap Logic Analyzer 采集并显示 FPGA 设计中的实时信号行

    2023年04月24日
    浏览(33)
  • async/await 的用法

    使用场景 在实际开发中,如果你遇到了等第一个请求返回数据完,再执行第二个请求(可能第二个请求要传的参数就是第一个请求接口返回的数据)这个问题。 代码 方法1: 方法2: 先请求接口1,获取到接口1返回结果后,将其作为接口2的参数,再去请求接口2 1、async 用于申明

    2024年02月07日
    浏览(37)
  • async/await 的理解和用法

    async放在函数前的一个修饰符,函数会默认返回一个Promise对象的resolve的值 1、await也是一个修饰符, 只能放在async定义的函数内 ,可以理解为 等待 2、await 修饰的是Promise对象: 获取Promise中返回的内容 (resolve或reject的参数), 且取到值后语句才会往下执行; 3、如果不是P

    2024年02月01日
    浏览(41)
  • typescript Awaited<Type>教程用法

    ts4.5发布了Awaited,但是很多人不明白Awaited的用法。 首先看一下官方的说明:这种类型旨在模拟函数await中的操作async,或 s.then()上的方法——特别是它们递归解包Promise的方式。 首先看一个例子: 这里我们可以正确得到bb类型为 string 。 在js中和ts中,await和.then()都能递归得到

    2024年02月04日
    浏览(50)
  • 微信小程序中await的用法

    在使用await的函数中,await需要被async包围,可以直接写在函数头上 在需要同步执行的语句前加await 微信开发者工具需要打开增强编译

    2024年02月11日
    浏览(47)
  • python中的async和await用法

    前言:此篇文章是在文心一言的辅助下完成的。 同步操作 :同步操作是指所有的操作都完成后,才返回给用户结果。当一个任务发出请求并等待响应时,如果未收到响应,该任务就会被阻塞,并一直等待直到收到响应为止。例如,在一个同步过程中,如果有一个函数需要较

    2024年04月27日
    浏览(32)
  • 记录--7个Js async/await高级用法

    JavaScript的异步编程已经从回调(Callback)演进到 Promise ,再到如今广泛使用的 async / await 语法。后者不仅让异步代码更加简洁,而且更贴近同步代码的逻辑与结构,大大增强了代码的可读性与可维护性。在掌握了基础用法之后,下面将介绍一些高级用法,以便充分利用 async / a

    2024年02月05日
    浏览(66)
  • Swift async/await 并发中如何将任务组(TaskGroup)转换为异步序列(AsyncSequence)

    在 Swift 新结构化并行模型的开发中, 提出了任务组(TaskGroup)和异步序列(AsyncSequence)的概念。有时候,为了简洁和效率方面的原因,我们需要将 TaskGroup 的结果转换为异步序列。 如上图所示,我们试图将任务组的结果转换为异步序列,但不幸失败了。 那么,最终我们到

    2024年02月12日
    浏览(44)
  • Java中的ReentrantLock实现原理

    在并发编程中,线程安全问题一直是非常重要的问题。Java中提供了多种解决线程安全问题的机制,其中一个比较常用的就是ReentrantLock。本文将介绍ReentrantLock的实现原理,从原子性、可见性等方面解释并结合源码分析,以便更好地理解在多线程环境下实现线程安全的过程。

    2024年02月01日
    浏览(44)
  • Go学习圣经:Go语言实现高并发CRUD业务开发

    现在 拿到offer超级难 ,甚至连面试电话,一个都搞不到。 尼恩的技术社群中(50+),很多小伙伴凭借 “左手云原生+右手大数据”的绝活,拿到了offer,并且是非常优质的offer, 据说年终奖都足足18个月 。 第二个案例就是:前段时间,一个2年小伙伴希望涨薪到18K, 尼恩把

    2024年02月11日
    浏览(53)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包