java并发编程--互斥锁, 读写锁及条件

2024-01-12 12:58

本文主要是介绍java并发编程--互斥锁, 读写锁及条件,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!


java.util.concurrent.locks包提供了锁和等待条件的接口和类, 可用于替代JDK1.5之前的同步(synchronized)和监视器机制(主要是Object类的wait(), notify(), notifyAll()方法).

 

互斥锁--Lock接口及其实现类ReentrantLock

所谓互斥锁, 指的是一次最多只能有一个线程持有的锁. 在jdk1.5之前, 我们通常使用synchronized机制控制多个线程对共享资源的访问. 而现在, Lock提供了比synchronized机制更广泛的锁定操作, Lock和synchronized机制的主要区别:

  • synchronized机制提供了对与每个对象相关的隐式监视器锁的访问, 并强制所有锁获取和释放均要出现在一个块结构中, 当获取了多个锁时, 它们必须以相反的顺序释放. synchronized机制对锁的释放是隐式的, 只要线程运行的代码超出了synchronized语句块范围, 锁就会被释放. 而Lock机制必须显式的调用Lock对象的unlock()方法才能释放锁, 这为获取锁和释放锁不出现在同一个块结构中, 以及以更自由的顺序释放锁提供了可能. 以下代码演示了在不同的块结构中获取和释放锁:
    public class LockTest {  private static Lock lock = new ReentrantLock();  public static void main(String[] args) {  lock.lock();  invokeMethod();  }  private static void invokeMethod() {  lock.unlock();  }  }  


  为了确保锁被释放, 通常会采用如下的代码形式:

    Lock lock = new ReentrantLock();  // 获取锁  lock.lock();  try {  // access the resource protected by this lock  } finally {  // 释放锁  lock.unlock();  }  


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

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

  • Lock提供了一个非块结构的获取锁尝试--tryLock(), 一个获取可中断锁的尝试--lockInterruptibly()和一个获取超时失效锁的尝试--tryLock(long time, TimeUnit unit).

|--boolean tryLock(): 如果锁可用, 则获取锁, 并立即返回true, 否则返回false. 该方法和lock()的区别在于, tryLock()只是"试图"获取锁, 如果锁不可用, 不会导致当前线程被禁用, 当前线程仍然继续往下执行代码. 而lock()方法则是一定要获取到锁, 如果锁不可用, 就一直等待, 在未获得锁之前,当前线程并不继续向下执行. 通常采用如下的代码形式调用tryLock()方法:

 

    Lock lock = new ReentrantLock();  if (lock.tryLock()) {  try {  // manipulate protected state  } finally {  lock.unlock();  }  } else {  // perform alternative actions  }  


此用法可确保如果获取了锁则会释放锁如果未获取锁则不会试图将其释放.

  • Lock的newCondition()方法可以获得与该锁绑定的Condition对象, Condition的详细介绍如下. 

 

条件--Condition

调用Condition对象的相关方法, 可以方便的挂起和唤醒线程. Object对象的wait(), notify(), notifyAll()方法当然也可以做到这一点, 但是Object对象的这些方法存在很不方便的地方--如果多个线程调用了obj的wait()方法而挂起, 那么我们无法做到调用obj的notify()和notifyAll()方法唤醒其中特定的一个线程. 而Condition对象就可以做到这一点. 具体的代码请参见我的上一篇博客http://coolxing.iteye.com/blog/1236696中的解法二部分.

  • void await(): 调用Condition对象的await()方法将导致当前线程被挂起, 并释放该Condition对象所绑定的锁. Condition对象只能通过Lock类的newCondition()方法获取, 因此一个Condition对象必然有一个与其绑定的Lock锁. 调用Condition对象的await()方法的前提是: 当前线程必须持有与该Condition对象绑定的锁, 否则程序可能抛出异常.
  • void signal(): 唤醒一个在该Condition对象上挂起的线程. 如果存在多个线程等待这个Condition对象的唤醒, 则随机选择一个. 线程被唤醒之前, 必须重新获取到锁(与该Condition对象绑定的Lock对象).
  • void signalAll(): 唤醒所有在该Condition对象上挂起的线程. 所有被唤醒的线程将竞争与该Condition对象绑定的锁, 只有获取到锁的线程才能恢复到运行状态.

 

读写锁--ReadWriteLock接口及其实现类ReentrantReadWriteLock

ReentrantReadWriteLock中定义了2个内部类, ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.WriteLock, 分别用来代表读取锁和写入锁. ReentrantReadWriteLock对象提供了readLock()和writeLock()方法, 用于获取读取锁和写入锁. 

 

  • 读取锁允许多个reader线程同时持有, 而写入锁最多只能有一个writter线程持有.
  • 读写锁的使用场合: 读取共享数据的频率远大于修改共享数据的频率. 在上述场合下, 使用读写锁控制共享资源的访问, 可以提高并发性能.
  • 如果一个线程已经持有了写入锁, 则可以再持有读写锁. 相反, 如果一个线程已经持有了读取锁, 则在释放该读取锁之前, 不能再持有写入锁.
  • 可以调用写入锁的newCondition()方法获取与该写入锁绑定的Condition对象, 此时与普通的互斥锁并没有什么区别. 但是调用读取锁的newCondition()方法将抛出异常. 

 

使用读写锁的一个例子:

 

    public class ReadWriteLockTest {  private static ReadWriteLock lock = new ReentrantReadWriteLock();  private static Person person = new Person("David Beckham", true);  public static void main(String[] args) {  new Thread() {  public void run() {  while(true) {  try {  lock.readLock().lock();  System.out.print("name = " + person.getName());  System.out.println(", isMan = " + person.isMan());  } finally {  lock.readLock().unlock();  }  }  };  }.start();  new Thread() {  public void run() {  boolean state = true;  while(true) {  try {  lock.writeLock().lock();  if (state) {  person.setName("Lady GaGa");  person.setMan(false);  state = false;  } else {  person.setName("David Beckham");  person.setMan(true);  state = true;  }  } finally {  lock.writeLock().unlock();  }  }  };  }.start();  }  }  class Person {  private String name;  private boolean isMan;  public Person(String name, boolean isMan) {  this.name = name;  this.isMan = isMan;  }  public String getName() {  return name;  }  public void setName(String name) {  this.name = name;  }  public boolean isMan() {  return isMan;  }  public void setMan(boolean isMan) {  this.isMan = isMan;  }  }  

不管程序运行多久, 也不会有人妖的出现.


该文章转载自http://coolxing.iteye.com/blog/1236909

这篇关于java并发编程--互斥锁, 读写锁及条件的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



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

相关文章

springboot控制bean的创建顺序

《springboot控制bean的创建顺序》本文主要介绍了spring-boot控制bean的创建顺序,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随... 目录1、order注解(不一定有效)2、dependsOn注解(有效)3、提前将bean注册为Bea

Java中的ConcurrentBitSet使用小结

《Java中的ConcurrentBitSet使用小结》本文主要介绍了Java中的ConcurrentBitSet使用小结,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、核心澄清:Java标准库无内置ConcurrentBitSet二、推荐方案:Eclipse

java中的Supplier接口解析

《java中的Supplier接口解析》Java8引入的Supplier接口是一个无参数函数式接口,通过get()方法延迟计算结果,它适用于按需生成场景,下面就来介绍一下如何使用,感兴趣的可以了解一下... 目录1. 接口定义与核心方法2. 典型使用场景场景1:延迟初始化(Lazy Initializati

Java中ScopeValue的使用小结

《Java中ScopeValue的使用小结》Java21引入的ScopedValue是一种作用域内共享不可变数据的预览API,本文就来详细介绍一下Java中ScopeValue的使用小结,感兴趣的可以... 目录一、Java ScopedValue(作用域值)详解1. 定义与背景2. 核心特性3. 使用方法

spring中Interceptor的使用小结

《spring中Interceptor的使用小结》SpringInterceptor是SpringMVC提供的一种机制,用于在请求处理的不同阶段插入自定义逻辑,通过实现HandlerIntercept... 目录一、Interceptor 的核心概念二、Interceptor 的创建与配置三、拦截器的执行顺

Python中Tkinter GUI编程详细教程

《Python中TkinterGUI编程详细教程》Tkinter作为Python编程语言中构建GUI的一个重要组件,其教程对于任何希望将Python应用到实际编程中的开发者来说都是宝贵的资源,这篇文... 目录前言1. Tkinter 简介2. 第一个 Tkinter 程序3. 窗口和基础组件3.1 创建窗

Java中Map的五种遍历方式实现与对比

《Java中Map的五种遍历方式实现与对比》其实Map遍历藏着多种玩法,有的优雅简洁,有的性能拉满,今天咱们盘一盘这些进阶偏基础的遍历方式,告别重复又臃肿的代码,感兴趣的小伙伴可以了解下... 目录一、先搞懂:Map遍历的核心目标二、几种遍历方式的对比1. 传统EntrySet遍历(最通用)2. Lambd

Spring Boot 中 RestTemplate 的核心用法指南

《SpringBoot中RestTemplate的核心用法指南》本文详细介绍了RestTemplate的使用,包括基础用法、进阶配置技巧、实战案例以及最佳实践建议,通过一个腾讯地图路线规划的案... 目录一、环境准备二、基础用法全解析1. GET 请求的三种姿势2. POST 请求深度实践三、进阶配置技巧1

springboot+redis实现订单过期(超时取消)功能的方法详解

《springboot+redis实现订单过期(超时取消)功能的方法详解》在SpringBoot中使用Redis实现订单过期(超时取消)功能,有多种成熟方案,本文为大家整理了几个详细方法,文中的示例代... 目录一、Redis键过期回调方案(推荐)1. 配置Redis监听器2. 监听键过期事件3. Redi

Spring Boot 处理带文件表单的方式汇总

《SpringBoot处理带文件表单的方式汇总》本文详细介绍了六种处理文件上传的方式,包括@RequestParam、@RequestPart、@ModelAttribute、@ModelAttr... 目录方式 1:@RequestParam接收文件后端代码前端代码特点方式 2:@RequestPart接