首页 > 编程语言 >java 各种锁

java 各种锁

时间:2023-05-24 16:55:06浏览次数:38  
标签:重入 各种 java synchronized 获取 线程 公平

乐观锁 悲观锁

这不是一种具体的锁,是一个广义的概念,可以认为所有的锁都是悲观或乐观的,java 中绝大部分锁都是悲观锁(synchronized、ReentrantLock等);也有乐观锁(原子类的递增、读写锁的读锁),java 的乐观锁都是 cas 实现的

乐观锁 顾名思义,老是假设自己在操作数据的同时一定会有别的数据也来操作,所以自己获取数据的时候要加锁,防止别的线程拿到这份数据
常见的实现有 synchronized、Lock的实现类、数据库更新删除和查询的 for update
特点:先加锁可以保证写操作时数据正确

悲观锁 老是觉得别的线程不会同时来操作数据,所以不会添加锁,只是再将要执行更改的时候进行判断,如果数据没发生更改就执行,如果发生了更改就 报错或重试
常见的方案有数据库的 version 机制,cas自旋锁
特点:不加锁的特点能够使其读操作的性能大幅提升

自旋锁(CAS)

乐观锁的基础,java 中的乐观锁是通过 CAS 实现的,Compare-and-Swap 或 Compare-and-Set,整个过程中没有加锁释放锁的步骤,所以也叫无锁(这种叫法并不合适,但是这样叫的人大有人在,准确的叫法应该是自旋锁)

原理是:如果要改变变量 flag 的值,比如要从 1 改成 2,如果当前 flag 的值是 1(flag 没变过),才改成 2,如果不是 1 就不改

ABA 问题:上面的例中,如果 flag 改成2,再改成 1,其实是变过的,但是最终表现的没有变过

java 中的乐观锁底层是 Unfase 类的 native 方法实现的,具体表现在各种原子类,比如 AtomicBoolean,AtomicLong,AtomicInteger
java 使用 AtomicStampedReference 解决原子类的 ABA 问题,原理是其内部维护了一个 Pair 对象,里面放了版本信息。其实解决 ABA 问题的原理就和数据库的乐观锁一样

公平锁 非公平锁

多个线程访问同一个锁,如果是公平锁,严格根据线程获取锁的先后顺序进行排队,先到先得;非公平锁就是后排队但是可能先得到锁(插队)

公平锁:线程一启动就进入 FIFO 队列,严格按照先后顺序获取到锁

非公平锁:线程启动时会尝试直接获取锁(插队),如果获取成功意味着插队成功。如果插队失败进入 FIFO 队列,这时和公平锁就一样了
对于公平锁,已经有别的线程获取到锁了,别的线程就要排队,等持有锁的线程释放锁然后唤醒一个等待中的线程。如果是非公平的,可能插队,直接就执行了,少唤醒的开销,所以 非公平锁吞吐量更高
如果一直都是后来的线程插队成功,队列里面的线程一直没机会被唤醒,就锁饿死了,所以 公平锁能缓解锁饥饿

java 中 synchronized 是非公平锁,ReentrantLock 默认是非公平锁,也可以是非公平锁(构造方法传入 true)

实现原理:通过一个 FIFO 队列来实现锁排队,公平锁就是严格按照排队先后顺序,持有锁的线程释放锁,唤醒队列中第一个线程;非公锁启动时判断是否能获取到锁,如果获取到就不排队了直接执行,如果没获取到锁就进入队列

重入锁 不可重入锁

在一个方法调用链中,如果外层方法获取了锁,内层方法自动也获取到该锁(前提是同一个锁,就不用再次获取锁),这就是可重入锁,反之就是不可重入锁,不可重入锁很容易造成死锁

public class Widget {
    // doSomething 获取到锁
    public synchronized void doSomething() {
        System.out.println("方法1执行...");
        // 如果是不可重入锁,doOthers 就要等 doSomething 释放锁,但是 doSomeing 没有执行完不会释放
        doOthers();
    }
    public synchronized void doOthers() {
        System.out.println("方法2执行...");
    }
}

目前 jdk1.8 没有已经实现了的不可重入锁(想想也是,太容易死锁了),ynchronized、Lock的实现类 都是可重入锁
实现原理是 AQS

读写锁

类似数据库的 S 和 X 锁,是一堆锁(上面说的这些锁,都是一个锁,不管读还是写,都要先拿到锁才能操作),也叫共享锁和排他锁/独占锁
多个线程能同时获取读锁,但是不能获取写锁(当读的场景多的时候写锁可能会饥饿)
只有一个线程能获取写锁,当写锁被持有时,别的线程不能获取读锁
java 的 ReentrantReadAndWriteLock 是已经实现了的读写锁
实现原理同 ReentrantLock 一样,也是 AQS

锁升级/锁膨胀

指的是 1.6 以后 synchronized 的优化方案,1.6 及之前一开始就是重量级锁,1.6 之后先是偏向锁,然后是 自旋锁(轻量级锁),最后是重量级锁
升级过程:

  • 代码压根不考虑并发(没有 synchronized 同步方法和同步代码块),这是当前对象是 无锁 状态
  • 使用了 synchronized,假设是同步方法,这时当前对象就从无锁变成有锁了,但是 synchronized 方法执行完不会释放锁,因为后续获取锁的线程可能还是当前线程(偏向第一次获取锁的线程,简言之就是先假设没有线程竞争),锁升级为 偏向锁
  • 当有别的线程参与竞争锁的时候,锁升级为 自旋锁(轻量级锁),cas 不会有加锁释放锁的操作,所以效率很高,所以 jvm 就让后面的线程自旋来获取锁
  • 一个线程持有锁,别的线程自旋,占着cpu做不了任何有效的业务操作,这就是忙等(busy-waiting),短时间还好(默认自旋10次,使用-XX:PreBlockSpin来更改),时间长了肯定也不合理,所以升级为 重量级锁

标签:重入,各种,java,synchronized,获取,线程,公平
From: https://www.cnblogs.com/hangychn/p/17184814.html

相关文章

  • Java大文件分片上传/多线程上传
    ​ javaweb上传文件上传文件的jsp中的部分上传文件同样可以使用form表单向后端发请求,也可以使用ajax向后端发请求    1.通过form表单向后端发送请求         <formid="postForm"action="${pageContext.request.contextPath}/UploadServlet"method="post"e......
  • python hmac_sha256 转为 java
    Javahmacsha256packagecom.example;importjava.security.InvalidKeyException;importjava.security.NoSuchAlgorithmException;importjavax.crypto.Mac;importjavax.crypto.spec.SecretKeySpec;importjavax.xml.bind.DatatypeConverter;publicclassMain......
  • java 多线程:synchronized 详解
    总结一个锁对象只能同时被一个线程持有,分为对象锁和类锁对象锁:每个对象都可以作为锁(几个不同的对象就是几个锁)类锁:字节码对象也能作为锁(全局唯一)同步方法不能自定义锁,只能是默认的锁(非静态:this,静态:class);同步代码块默认的锁和方法一样(非静态:this,静态:class,普通方法里面可以......
  • Java开启异步的两种方式
    二、Java开启异步的两种方式1、注解开启:@Async1.1、配置异步的线程池必须配置异步线程池,否则异步不会生效。@EnableAsync注解:指定异步线程池。不指定默认使用:SimpleAsyncTaskExecutor线程池SimpleAsyncTaskExecutor是一个最简单的线程池,它没有任何的线程相关参数配置,它会为......
  • 用命令行工具运行java文件
     1、若java文件有packagepackagequitStu;publicclassMain{publicstaticvoidmain(String[]args){for(Stringstr:args){if(str.equals("11")){System.out.println("-v1.0");brea......
  • Java如何生成随机数?要不要了解一下!
    前言我们在学习Java基础时就知道可以生成随机数,可以为我们枯燥的学习增加那么一丢丢的乐趣。本文就来介绍Java随机数。一、Random类介绍在Java中使用Random工具类来生成随机数,该类在java.util包下,在JDK1.0版本就存在了。Random单词本身就是随机、随意、任意的意思......
  • Java配置线程池
    一、Java配置线程池1、线程池分类、其他1.1、分类IO密集型和CPU密集型任务的特点不同,因此针对不同类型的任务,选择不同类型的线程池可以获得更好的性能表现。1.1.IO密集型任务​ IO密集型任务的特点是需要频繁读写磁盘、网络或者其他IO资源,执行时间长,CPU占用率较低。对......
  • 流程表单JavaScript代码
    ----订单流程-----------//表单加载初始化时functionpreinit(){}//表单加载完成,isrun代表流程是否流转中1-是,0-否functionLoaded(isrun){$("#om_order_status").attr("disabled","disabled");......
  • java入门2..0
    java的运行原理1.在本地磁盘中创建一个文本文件为Demo.java的源文件2.在源文件中编写java代码如下:publicclassDemopublicstaticvoid,main(String[]args){System.out.println(" ");}3.在当前文件目录下。输入cmd跳转到dos窗口4.通过java编译源文件。会生成字节码文件......
  • 剑指 Offer II 018(Java). 有效的回文(简单)
    题目:给定一个字符串s,验证s 是否是 回文串 ,只考虑字母和数字字符,可以忽略字母的大小写。本题中,将空字符串定义为有效的 回文串 。 示例1:输入:s="Aman,aplan,acanal:Panama"输出:true解释:"amanaplanacanalpanama"是回文串示例2:输入:s="raceacar"......