前言

Java 的锁实现,有 Synchronized 和 Lock。上一篇文章深入分析了 Synchronized 的实现原理:由Java 15废弃偏向锁,谈谈Java Synchronized 的锁机制

本篇文章深入分析 Lock 的实现,以及对比其与 Synchronized 的不同。

Synchronized 与 Lock 的对比

  • 实现方式:Synchronized 由 JVM 实现;Lock 由 Java 底层代码实现
  • 锁获取:Synchronized 是 JVM 隐式获取,不用 Java 代码;Lock 由 Java 代码实现,有多种获取方式
  • 锁的释放:Synchronized 是 JVM 隐式释放,不用 Java 代码;Lock 可通过 Lock.unlock(),在 finally 中释放
  • 锁的类型:Synchronized 是非公平、可重入的Lock 是非公平性、公平性、可重入的
  • 锁的中断:Synchronized 不支持中断,Lock 支持中断

实现原理

Lock 是一个接口类,其接口方法定义:

  • lock():获取锁
  • lockInterruptibly():如果当前线程未被中断,则获取锁,可以响应中断
  • tryLock():仅在调用时锁为空闲状态才获取该锁,可以响应中断
  • tryLock(long time, TimeUnit unit):如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁
  • unlock():释放锁
  • newCondition():返回绑定到此 Lock 实例的新 Condition 实例

基础原理就不赘述了,Lock 接口的常用类:

  • ReentrantLock(重入锁)
  • ReentrantReadWriteLock(可重入的读写锁)

其都是依赖 AQS 实现的。AQS 类结构中包含一个基于链表实现的等待队列(CLH 队列),用于存储所有阻塞的线程,AQS 中还有一个 state 变量,表示加锁状态。

ReentrantLock

ReentrantReadWriteLock

ReentrantLock 是独占锁,对于同一份数据,如果一个线程读数据,另一个线程在写数据,那么读到的数据与最终的数据可能不一致。

在实际的业务场景中,读操作远远大于写操作。在多线程编程中,读操作不会修改共享资源的数据。针对读多写少的场景,我们可以使用 ReentrantReadWriteLock 来优化,ReentrantReadWriteLock 内部维护了 2 个锁:读锁 ReadLock,写锁 WriteLock

规则简单概括为:

  • 如果写锁没有被占用,就可以获取读锁
  • 如果读锁没有被占用,才可以获取写锁

下面的测试代码,因为读锁和写锁是同时 lock 的,所以会死锁。

@Test
public void testReadWriteLock() {
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLock().lock();
lock.writeLock().lock();
System.out.println("Hello");
lock.readLock().unlock();
lock.writeLock().unlock();
}

StampedLock

上述 ReentrantReadWriteLock 会有一个问题:在读很多,写很少的情况下,线程会因一直无法获取到锁而处于等待状态。

在 JDK 1.8 中,Java 提供了 StampedLock,有三种模式:

  • 悲观读
  • 乐观读

一个写线程获取写锁,通过 WriteLock 获取票据 stamp,WriteLock 是一个独占锁,unlockWrite 需要传递参数 stamp。

不一样的地方在于读过程。线程会先通过乐观锁 tryOptimisticRead 获取票据 stamp,如果当前没有线程持有写锁,则会返回一非 0 的 stamp 信息。线程获取该 stamp 后,会拷贝一份共享资源到房发展。

之后方法还需要调用 validate,验证之前调用 tryOptimisticRead 返回的 stamp 在当前是否有其它线程持有了写锁,如果是,那么 validate 会返回 0,升级为悲观锁;否则就可以使用该 stamp 版本的锁对数据进行操作。

总结

相比于 Synchronized 同步锁,Lock 实现的锁更加灵活:

  • 可以分为读写锁,优化读大于写的场景
  • 可以中断
  • 可以超时
  • 可以区分公平性

公众号

coding 笔记、点滴记录,以后的文章也会同步到公众号(Coding Insight)中,希望大家关注_

代码和思维导图在 GitHub 项目中,欢迎大家 star!

深入分析 Java Lock 同步锁的更多相关文章

  1. Lock同步锁

    Lock同步锁 一.前言 在Java 5.0 之前,协调共享对象的访问时可以使用的机制只有synchronized 和volatile .Java 5.0 后增加了一些新的机制,但并不是一种替代内置锁 ...

  2. JUC--Callable 以及Lock同步锁

    /** * 一.创建执行线程的方式三:实现Callable接口.相较于实现Runnable接口方式,方法可以有返回值,并且可以抛出异常 * 二.callable 需要FutureTask实现类的支持. ...

  3. 7.生产者消费者 案例 (使用Lock 同步锁 方式,使用Condition完成线程之间的通信)

    /* * 生产者消费者 案例 (使用Lock 同步锁 方式,使用Condition完成线程之间的通信) * */ public class TestProductorAndConsumerForLoc ...

  4. Java并发编程之Lock(同步锁、死锁)

    这篇文章是接着我上一篇文章来的. 上一篇文章 同步锁 为什么需要同步锁? 首先,我们来看看这张图. 这是一个程序,多个对象进行抢票. package MovieDemo; public class T ...

  5. Lock同步锁--线程同步

    Lock-同步锁 Lock是java5提供的一个强大的线程同步机制--通过显示定义同步锁对象来实现同步.Lock可以显示的加锁.解锁.每次只能有一个线程对lock对象加锁. Lock有ReadLock ...

  6. Java多线程同步锁的理解

    java主要通过synchronized的关键字来实现的.让我们从一个买票程序说起吧. package com.day04; /** * * @author Administrator 问题描述:使用 ...

  7. Java线程同步锁

    把synchronized当作函数修饰符时,示例代码如下: Public synchronized void method(){ //-. } 这也就是同步方法,那这时synchronized锁定的是 ...

  8. java提高同步锁的几点建议

    1.减少锁的持有时间,只对关键的代码块加锁,减少synchronized锁内部的无关模块: 2.减小锁粒度,如Collections.synchronizedMap(map)返回线程安全的map会锁整 ...

  9. python笔记9 线程进程 threading多线程模块 GIL锁 multiprocessing多进程模块 同步锁Lock 队列queue IO模型

    线程与进程 进程 进程就是一个程序在一个数据集上的一次动态执行过程.进程一般由程序.数据集.进程控制块三部分组成.我们编写的程序用来描述进程要完成哪些功能以及如何完成:数据集则是程序在执行过程中所需要 ...

随机推荐

  1. 手写简易React-Fiber

    1.首先创建createElement函数 1 function createElement ( 2 type, 3 config, 4 ...children 5 ) { 6 7 const pro ...

  2. IDEA安装leetcode editor插件

    leetcode > https://leetcode-cn.com/ 本地idea刷题可以直接同步提交,测试等相关操作 需要安装leetcode editor插件 1.idea setting ...

  3. TCP粘包问题的解决方案02——利用readline函数解决粘包问题

      主要内容: 1.read,write 与 recv,send函数. recv函数只能用于套接口IO ssize_t recv(int sockfd,void * buff,size_t len,i ...

  4. 美团面试官问我: ZGC 的 Z 是什么意思

    本文的阅读有一定的门槛,请先了解 GC 的基本只知识. 现代垃圾收集器的演进大部分都是往减少停顿方向发展. 像 CMS 就是分离出一些阶段使得应用线程可以和垃圾回收线程并发,当然还有利用回收线程的并行 ...

  5. Makefile 指定源文件目录 make

    top=$(CURDIR) SRC_DIR=$(top)/src BUILD_DIR=$(SRC_DIR) src=$(wildcard $(SRC_DIR)/*.c) obj=$(patsubst ...

  6. mds/journal.cc: 2929: FAILED assert解决

    前言 在处理一个其他双活MDS无法启动环境的时候,查看mds的日志看到了这个错误mds/journal.cc: 2929: FAILED assert(mds->sessionmap.get_v ...

  7. Vue 组件化开发之插槽

    插槽的作用 相信看过前一篇组件化开发后,你对组件化开发有了新的认识. 插槽是干什么的呢?它其实是配合组件一起使用的,让一个组件能够更加的灵活多变,如下图所示,你可以将组件当作一块电脑主板,将插槽当作主 ...

  8. Dubbo 服务导出-Version2.7.5

    1.源码分析 1.1分析服务导出入口 当容器为spring是dubbo会为容器注册两个监听器:DubboLifecycleComponentApplicationListener和DubboBoots ...

  9. Spring @Autowired 注解自动注入流程是怎么样?

    面试中碰到面试官问:"Spring 注解是如果工作的?",当前我一惊,完了这不触及到我的知识误区了吗?,还好我机智,灵机一动回了句:Spring 注解的工作流程倒还没有看到,但是我 ...

  10. Java 中常见的细粒度锁实现

    上篇文章大致说了下 ReentrantLock 类的使用,对 ReentrantLock 类有了初步的认识之后让我们一起来看下基于 ReentrantLock 的几种细粒度锁实现. 这里我们还是接着用 ...