【并发】Lock与ReentrantLock

2024-09-03 14:58
文章标签 并发 lock reentrantlock

本文主要是介绍【并发】Lock与ReentrantLock,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1 Lock基本使用

Lock能实现代码同步,它比synchronized更具灵活性,什么时候锁住,什么时候释放锁等都是看得见的,使用时必须使用try{}finally{},意思是万一发生异常或者错误都可以释放锁。

try{
}finally{//释放锁
}
  • 使用示例
public class SaleTicket implements Runnable {private int ticket = 10 * 100000;private Lock mLock = new ReentrantLock();@Overridepublic void run() {try {while (ticket > 0) {mLock.lock();if(ticket>0){if (ticket % 100000 == 0) {System.out.println("名称:" + Thread.currentThread().getName() + "窗口卖出第" + (ticket / 100000) + "张票");}ticket--;}}} finally {mLock.unlock();}}
}
        Runnable saleTicket = new SaleTicket();Thread thread1 = new Thread(saleTicket);Thread thread2 = new Thread(saleTicket);Thread thread3 = new Thread(saleTicket);Thread thread4 = new Thread(saleTicket);thread1.start();thread2.start();thread3.start();thread4.start();

结果:
image.png

2 ReentrantLock

ReentrantLock有两种锁机制:忽略中断锁和响应中断锁。如:如果A、B两个线程去竞争锁,A获得锁,B等待,但A线程要做的事情太多,一直不返回锁,B线程就想先中断自己不再等待这个锁,转而去处理其他事情。这时候ReentrantLock提供了2种机制,第一种,B线程中断自己(或者别的线程中断它),但是ReentrantLock不去响应,继续让B线程等待,这就是忽略中断锁(lock());第二种,B线程中断自己(或者别的线程中断它),ReentrantLock处理了这个中断,并且不再等待这个锁的到来,完全放弃,这就是响应中断锁(lockInterruptibly())。

  • 响应中断锁示例:
public class BufferInterruptibly {private ReentrantLock mLock = new ReentrantLock();public void write(){mLock.lock();try {long startTime = System.currentTimeMillis();System.out.println("开始往这个buff写入数据…");for (; ; ) {if (System.currentTimeMillis() - startTime > Integer.MAX_VALUE) {break;}}System.out.println("终于写完了");}finally {mLock.unlock();}}public void read() throws InterruptedException{mLock.lockInterruptibly();try{System.out.println("从这个buff读数据");}finally {mLock.unlock();}}
}
final BufferInterruptibly buff = new BufferInterruptibly();Thread write = new Thread(new Runnable() {@Overridepublic void run() {buff.write();}});final Thread read = new Thread(new Runnable() {@Overridepublic void run() {try{Thread.sleep(500);buff.read();}catch (Exception e){System.out.println("我不读了");}System.out.println("读结束");}});write.start();read.start();new Thread(new Runnable() {@Overridepublic void run() {long start = System.currentTimeMillis();for (;;){if (System.currentTimeMillis()-start>500){System.out.println("不等了,尝试中断");read.interrupt();break;}}}}).start();

结果:
image.png

ReentrantLock叫做重入锁,意思是外层方法获取到锁后,内层方法会自动获取到锁。synchronized也是可重入锁。我们先看一下synchronized锁的可重入性

class SychronizedReentrant implements Runnable{private Object object = new Object();/*** 方法一调用方法二*/public void method1(){synchronized (object){System.out.println(Thread.currentThread().getName()+ " method1 ");method2();}}/**方法二,打印获取obj锁如果是同一线程,锁不可以重入,method2需要等待method1释放锁*/public void method2(){synchronized (object){System.out.println(Thread.currentThread().getName()+ " method2 ");}}@Overridepublic void run() {method1();}
}

结果

Thread-0 method1
Thread-0 method2

method1有一个同步块,同步块中调用了method2,而method2中也有个同步块,一般来说,synchronized块里面的内容执行完才会释放锁,其它synchronized块才能竞争到锁,而向上述调用步骤,明显是外部方法的synchronized还没有释放锁,内部方法的synchronized就可以得到锁,这就是重入锁。

例子(摘抄网上):

package tags;import java.util.Calendar;public class TestLock {private ReentrantLock lock = null;public int data = 100;     // 用于线程同步访问的共享数据public TestLock() {lock = new ReentrantLock(); // 创建一个自由竞争的可重入锁}public ReentrantLock getLock() {return lock;}public void testReentry() {lock.lock();Calendar now = Calendar.getInstance();System.out.println(now.getTime() + " " + Thread.currentThread()    + " get lock.");}public static void main(String[] args) {TestLock tester = new TestLock();//1、测试可重入tester.testReentry();tester.testReentry(); // 能执行到这里而不阻塞,表示锁可重入tester.testReentry(); // 再次重入// 释放重入测试的锁,要按重入的数量解锁,否则其他线程无法获取该锁。tester.getLock().unlock();tester.getLock().unlock();tester.getLock().unlock();//2、测试互斥// 启动3个线程测试在锁保护下的共享数据data的访问new Thread(new workerThread(tester)).start();new Thread(new workerThread(tester)).start();new Thread(new workerThread(tester)).start();}// 线程调用的方法public void testRun() throws Exception {lock.lock();Calendar now = Calendar.getInstance();try {// 获取锁后显示 当前时间 当前调用线程 共享数据的值(并使共享数据 + 1)System.out.println(now.getTime() + " " + Thread.currentThread()+ " accesses the data " + data++);Thread.sleep(1000);} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();}}
}// 工作线程,调用TestServer.testRun
class workerThread implements Runnable {private TestLock tester = null;public workerThread(TestLock testLock) {this.tester = testLock;}public void run() {try {tester.testRun();} catch (Exception e) {e.printStackTrace();}}
}

这篇关于【并发】Lock与ReentrantLock的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1133283

相关文章

高并发环境中保持幂等性

在高并发环境中保持幂等性是一项重要的挑战。幂等性指的是无论操作执行多少次,其效果都是相同的。确保操作的幂等性可以避免重复执行带来的副作用。以下是一些保持幂等性的常用方法: 唯一标识符: 请求唯一标识:在每次请求中引入唯一标识符(如 UUID 或者生成的唯一 ID),在处理请求时,系统可以检查这个标识符是否已经处理过,如果是,则忽略重复请求。幂等键(Idempotency Key):客户端在每次

Java并发编程之——BlockingQueue(队列)

一、什么是BlockingQueue BlockingQueue即阻塞队列,从阻塞这个词可以看出,在某些情况下对阻塞队列的访问可能会造成阻塞。被阻塞的情况主要有如下两种: 1. 当队列满了的时候进行入队列操作2. 当队列空了的时候进行出队列操作123 因此,当一个线程试图对一个已经满了的队列进行入队列操作时,它将会被阻塞,除非有另一个线程做了出队列操作;同样,当一个线程试图对一个空

java线程深度解析(五)——并发模型(生产者-消费者)

http://blog.csdn.net/Daybreak1209/article/details/51378055 三、生产者-消费者模式     在经典的多线程模式中,生产者-消费者为多线程间协作提供了良好的解决方案。基本原理是两类线程,即若干个生产者和若干个消费者,生产者负责提交用户请求任务(到内存缓冲区),消费者线程负责处理任务(从内存缓冲区中取任务进行处理),两类线程之

java线程深度解析(四)——并发模型(Master-Worker)

http://blog.csdn.net/daybreak1209/article/details/51372929 二、Master-worker ——分而治之      Master-worker常用的并行模式之一,核心思想是由两个进程协作工作,master负责接收和分配任务,worker负责处理任务,并把处理结果返回给Master进程,由Master进行汇总,返回给客

深入解析秒杀业务中的核心问题 —— 从并发控制到事务管理

深入解析秒杀业务中的核心问题 —— 从并发控制到事务管理 秒杀系统是应对高并发、高压力下的典型业务场景,涉及到并发控制、库存管理、事务管理等多个关键技术点。本文将深入剖析秒杀商品业务中常见的几个核心问题,包括 AOP 事务管理、同步锁机制、乐观锁、CAS 操作,以及用户限购策略。通过这些技术的结合,确保秒杀系统在高并发场景下的稳定性和一致性。 1. AOP 代理对象与事务管理 在秒杀商品

PostgreSQL中的多版本并发控制(MVCC)深入解析

引言 PostgreSQL作为一款强大的开源关系数据库管理系统,以其高性能、高可靠性和丰富的功能特性而广受欢迎。在并发控制方面,PostgreSQL采用了多版本并发控制(MVCC)机制,该机制为数据库提供了高效的数据访问和更新能力,同时保证了数据的一致性和隔离性。本文将深入解析PostgreSQL中的MVCC功能,探讨其工作原理、使用场景,并通过具体SQL示例来展示其在实际应用中的表现。 一、

使用协程实现高并发的I/O处理

文章目录 1. 协程简介1.1 什么是协程?1.2 协程的特点1.3 Python 中的协程 2. 协程的基本概念2.1 事件循环2.2 协程函数2.3 Future 对象 3. 使用协程实现高并发的 I/O 处理3.1 网络请求3.2 文件读写 4. 实际应用场景4.1 网络爬虫4.2 文件处理 5. 性能分析5.1 上下文切换开销5.2 I/O 等待时间 6. 最佳实践6.1 使用 as

Go并发模型:流水线模型

Go作为一个实用主义的编程语言,非常注重性能,在语言特性上天然支持并发,Go并发模型有多种模式,通过流水线模型系列文章,你会更好的使用Go的并发特性,提高的程序性能。 这篇文章主要介绍流水线模型的流水线概念,后面文章介绍流水线模型的FAN-IN和FAN-OUT,最后介绍下如何合理的关闭流水线的协程。 Golang的并发核心思路 Golang并发核心思路是关注数据流动。数据流动的过程交给cha

Java 入门指南:Java 并发编程 —— 并发容器 ConcurrentLinkedDeque

文章目录 ConcurrentLinkedDeque特点构造方法常用方法使用示例注意事项 ConcurrentLinkedDeque ConcurrentLinkedDeque 是 Java 并发工具包(java.util.concurrent 包)中的一个线程安全的双端队列(Deque)实现,实现了 Deque 接口。它使用了链表结构,并且针对高并发环境进行了优化,非常适合

并发线程基础(一)

一、多个线程多个锁(lock) 概念:每个线程拿到自己的锁之后,获取线程锁之后,执行synchronized方法内的内容。      (一)synchronized 重入          概念:我比较喜欢通过自己的语言去记住和简化概念,重入:也叫“递归锁”,指同一个线程的外层方法获得锁之后,该方法里面调用修饰锁的方法,仍然可以获取锁,相互不受影响的。           语言话:同个类中