【Java 中锁的种类】

文章目录

  • 一、公平锁和非公平锁
  • 二、可重入锁(递归锁)
  • 三、自旋锁
  • 四、独占锁(写锁)/共享锁(读锁)/互斥锁


提示:以下是本篇文章正文内容,下面案例可供参考

一、公平锁和非公平锁

遵守先来后到的是公平锁,不遵守的是非公平锁

  1. synchronized 是非公平锁
  2. ReentrantLock 默认是非公平锁,可通过传入参数来变成公平锁。
	// 默认的无参构造方法是非公平锁的
    public ReentrantLock() {
        sync = new NonfairSync();
    }

    /**
     * fair == true,是公平锁
     * fair == false,是非公平锁
     */
    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

优缺点

  • 公平锁能够保证任务的有序进行,所有的线程都能得到资源,不会饿死在队列中。缺点是吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。
  • 非公平锁可以减少CPU唤醒线程的开销,整体的吞吐量比公平锁高,CPU也不必取唤醒所有线程,会减少唤起线程的数量。缺点是可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饥饿现象。

二、可重入锁(递归锁)

指的是同一线程外层函数获得锁之后﹐内层递归函数仍然能获取该锁的代码,在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁,也就是说线程可以进入任何一个它已经拥有的锁所同步着的代码块

synchronized 和 ReentrantLock 都是可重入锁

代码示例

class Phone implements Runnable{

    // 证明 synchronized 是可重入锁
    
    /**
     * 两个 synchronized 能够嵌套式使用
     * 
     * 
     */
    public synchronized void sendMessage() {
        // 调用该方法第一次加锁
        System.out.println(Thread.currentThread().getName() + "\tsendMessage");
        // 第二次加锁,使用 sendMessage 的锁,即共用同一把锁
        sendEmail();
    }

    public synchronized void sendEmail() {
        System.out.println(Thread.currentThread().getName() + "\tsendEmail");
    }



    // 证明 ReentrantLock
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        get();
    }

    public void get() {
        // ReentrantLock 需要手动加锁解锁
        // 但是要一一配对,只要配对,多少都可以
        // 如果这里 lock 两次,unlock 一次就会导致线程一直阻塞等待解锁
        lock.lock();
//        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\tget");
            // 方法内部再次加锁
            set();
        } finally {
            // 不配对,会一直死锁
            lock.unlock();
//            lock.unlock();
        }
    }

    public void set() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + "\tset");
        } finally {
            lock.unlock();
        }
    }
}


public class Main {
    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(() -> {
            phone.sendMessage();
        }, "t1").start();

        new Thread(() -> {
            phone.sendEmail();
        }, "t2").start();

        Thread.sleep(1000);

        System.out.println();
        System.out.println("#######################");
        System.out.println();

        new Thread(phone, "t3").start();
        new Thread(phone, "t4").start();
    }
}


输出结果

嵌套调用成功,输出的顺序和我们预想的相同。

在这里插入图片描述

synchronized 加锁和释放锁的原理

monitorenter 和 monitorexit 指令,monitor计数器

monitorenter指令:

  1. monitor计数器为0,意味着目前还没有被获得,那这个线程就会执行一次 monitorenter 指令立刻获得这把锁,然后把锁计数器+1,一旦+1,别的线程再想获取,就需要等待。
  2. 如果这个monitor已经拿到了这个锁的所有权,又重入了这把锁,那锁计数器就会累加,变成2,并且随着重入的次数,会一直累加,但是累加的仅仅是锁计数器,不会再执行一次 monitorenter 指令。(即重复加锁,monitorenter 指令只执行一次,锁计数器不断累加
  3. 这把锁已经被别的线程获取了,等待锁释放。

monitorexit指令:将锁计数器减1,每执行一次 monitorexit 命令锁计数器就减1(这一点与 monitorenter 不同),当锁计数器为0,释放锁。


三、自旋锁

是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU

synchronized 和 ReentrantLock 都是自旋锁,他们都会根据使用场景自动调整自旋时间

代码示例

class SpinLockDemo {
    // 原子引用线程
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    public void myLock() {
        // 此时 atomicReference 内的线程为 null
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "\tcome in");
        // CAS 自旋
        // 将 thread 内的线程赋给 atomicReference,一直自旋直到成功
        while (!atomicReference.compareAndSet(null, thread)) {

        }
    }

    public void myUnLock() {
        Thread thread = Thread.currentThread();
        // 解锁
        atomicReference.compareAndSet(thread, null);
        System.out.println(Thread.currentThread().getName() + "\tcome out");
    }
}



public class Main {
    public static void main(String[] args) {
       SpinLockDemo spinLockDemo = new SpinLockDemo();

        new Thread(() -> {
            spinLockDemo.myLock();
            // 暂停一会线程,模拟一直占用,不能释放锁
            try {
				System.out.println("阻塞中......");
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            spinLockDemo.myUnLock();
        }, "AA").start();

    }
}

输出结果

在这里插入图片描述


四、独占锁(写锁)/共享锁(读锁)/互斥锁

独占锁:指该锁一次只能被一个线程所持有。对 ReentrantLock 和 Synchronized 而言都是独占锁
共享锁:指该锁可被多个线程所持有

ReentrantReadWriteLock

  • 对 ReentrantReadWriteLock 其读锁是共亨锁,其写锁是独占锁。读写锁分离既保证了数据的一致性,又保证了高并发性
  • 读锁的共享锁可保证并发读是非常高效的,读写,写读,写写的过程是互斥的(互斥锁)
  • 写操作是独占的,原子的,不许分割。

代码示例

class MyCache {// 资源类(缓存操作,需要较高地并发操作)
    private volatile Map<String, Object> map = new HashMap<>();
    // 这个锁不足以满足,多个线程的同时读写
//    private Lock lock = new ReentrantLock();
    // 读写锁分离
//    private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();// XXXXXXXXX



    /**
     * 写操作,不可共享
     * @param key
     * @param val
     * @throws InterruptedException
     */
    public void put(String key, Object val) throws InterruptedException {
        try {
            // 拿到 writeLock,加锁
//            readWriteLock.writeLock().lock();// XXXXXXXXX

            System.out.println(Thread.currentThread().getName() + "\t正在写入:" + key);
            Thread.sleep(300);
            map.put(key, val);
            System.out.println(Thread.currentThread().getName() + "\t写入完成");
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 解锁
//            readWriteLock.writeLock().unlock();// XXXXXXXXX
        }
    }


    /**
     * 读操作,可以共享
     * @param key
     * @throws InterruptedException
     */
    public void get(String key) throws InterruptedException {
        try {
            // 拿到 readLock 加锁
//            readWriteLock.readLock().lock();// XXXXXXXXX

            System.out.println(Thread.currentThread().getName() + "\t正在读取");
            Thread.sleep(300);
            System.out.println(Thread.currentThread().getName() + "\t读取完成:" + map.get(key));
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            // 解锁
//            readWriteLock.readLock().unlock();// XXXXXXXXX
        }
    }
}


public class ZZZZZZZZZZ {
    public static void main(String[] args) {
        MyCache myCache = new MyCache();
        // 写
        for (int i = 1; i <= 5; i++) {
            final int tmpInt = i;
            new Thread(() -> {
                try {
                    myCache.put(tmpInt + "", tmpInt + "");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }

        // 读
        for (int i = 1; i <= 5; i++) {
            final int tmpInt = i;
            new Thread(() -> {
                try {
                    myCache.get(tmpInt + "");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }
    }
}



输出结果(加锁前)

在这里插入图片描述

输出结果(加锁后)

将代码注释有XXXXXXXXX的代码解开,就是加锁后版本

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/274241.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Jmeter 性能 —— 监控服务器!

Jmeter 监控Linux需要三个文件 JMeterPlugins-Extras.jar (包&#xff1a;JMeterPlugins-Extras-1.4.0.zip)JMeterPlugins-Standard.jar (包&#xff1a;JMeterPlugins-Standard-1.4.0.zip)ServerAgent-2.2.3.zip 1、Jemter 安装插件 在插件管理中心的搜索Servers Performa…

基于ChatGpt,Java,SpringBoot,Vue,Milvus向量数据库的定制化聊天Web demo

customized chat GitHub - bigcyy/customized-chatgpt: 基于ChatGpt&#xff0c;Java&#xff0c;SpringBoot&#xff0c;Vue&#xff0c;Milvus向量数据库的定制化聊天Web demo 简介 基于ChatGpt&#xff0c;Java&#xff0c;SpringBoot&#xff0c;Vue&#xff0c;Milvus向…

【MyBatis-Plus】进阶之乐观锁、悲观锁逻辑删除分页和查询构造器的使用

目录 一、乐观锁、悲观锁 1、什么是乐观锁和悲观锁 ①乐观锁&#xff08;Optimistic Locking&#xff09;: ②悲观锁&#xff08;Pessimistic Locking&#xff09;: ③实现方式 2、乐观锁和悲观锁的区别 ①乐观锁&#xff08;Optimistic Locking&#xff09; ②悲观锁&…

【头歌实训】PySpark Streaming 数据源

文章目录 第1关&#xff1a;MySQL 数据源任务描述相关知识PySpark JDBC 概述PySpark JDBCPySpark Streaming JDBC 编程要求测试说明答案代码 第2关&#xff1a;Kafka 数据源任务描述相关知识Kafka 概述Kafka 使用基础PySpark Streaming Kafka 编程要求测试说明答案代码 第1关&a…

亿赛通电子文档安全管理系统 linkfilterservice 未授权漏洞

产品简介 亿赛通电子文档安全管理系统&#xff0c;&#xff08;简称&#xff1a;CDG&#xff09;是一款电子文档安全加密软件&#xff0c;该系统利用驱动层透明加密技术&#xff0c;通过对电子文档的加密保护&#xff0c;防止内部员工泄密和外部人员非法窃取企业核心重要数据资…

在Spring Cloud中使用Ribbon完成一个简单的负载均衡demo

Spring Cloud系列断更了有一段时间了&#xff0c;这段时间最近都在忙着项目上的事&#xff0c;天天修复bug以及调整需求&#xff0c;反正各种操劳&#xff0c;了解业务需求&#xff0c;然后开发相关功能&#xff0c;很久都没碰Spring Cloud系列的相关文章了&#xff0c;最近回头…

ASP.Net实现新闻添加查询(三层架构,含照片)

目录 演示功能&#xff1a; 点击启动生成页面 点击搜索模糊查询 点击添加跳转新界面 ​编辑 点击Button添加 步骤&#xff1a; 1、建文件 ​编辑 2、添加引用关系 3、根据数据库中的列写Models下的XueshengModels类 4、DAL下的DBHelper&#xff08;对数据库进行操作…

Win10电脑蓝牙默认音量100的设置教程

在Win10电脑操作过程中&#xff0c;用户想设置连接蓝牙后音量默认是100&#xff0c;但不知道具体的设置操作步骤。这时候用户需要打开Win10系统上的注册表&#xff0c;点击修改注册表来完成这一设置&#xff0c;下面就是Win10电脑蓝牙默认音量100的设置教程介绍&#xff0c;帮助…

【网络安全 | 指纹识别工具】WhatWeb使用详析

前言 WhatWeb 是一款用于识别 Web 应用程序和 Web 服务器的开源工具。它可以识别网站使用的编程语言、Web 框架、Web 服务器软件、Web 应用程序等信息&#xff0c;从而帮助安全测试人员快速了解目标网站的技术特征&#xff0c;发现可能存在的漏洞。 本文将对 WhatWeb 的使用方法…

使用Rust发送邮件

SMTP协议与MIME协议 SMTP&#xff08;简单邮件传输协议,Simple Mail Transfer Protocol&#xff09;是一种用于发送和接收电子邮件的互联网标准通信协议。它定义了电子邮件服务器如何相互发送、接收和中继邮件。SMTP 通常用于发送邮件&#xff0c;而邮件的接收通常由 POP&#…

在wps里导入Mathtype、改变字体

1 在wps里导入Mathtype 开发工具--加载项 2. 在“模板和加载项”窗口中再点击“添加” 3.找到mathtype安装路径下面的“OfficeSupprot”&#xff0c;这时会看到有“32”和“64”两个文件夹&#xff0c;分别对应WPS软件的系统&#xff08;任务管理器可以直接查看wps版本&#x…

three.js实现3D汽车展厅效果展示

项目搭建 本案例还是借助框架书写three项目&#xff0c;借用vite构建工具搭建vue项目&#xff0c;搭建完成之后&#xff0c;用编辑器打开该项目&#xff0c;在终端执行 npm i 安装一下依赖&#xff0c;安装完成之后终端在安装 npm i three 即可。 因为我搭建的是vue3项目&…

理解 Go Mod Init

初始化Go模块和管理依赖的全面指南 go mod init 是Go编程语言&#xff08;通常称为Golang&#xff09;中用于初始化新Go模块的命令。在Go中&#xff0c;一个模块是一组相关的Go包&#xff0c;它们作为一个单元一起进行版本控制。通常&#xff0c;在项目目录的根目录下使用 go m…

NLP论文阅读记录 - 01 | 2021 神经抽象摘要方法及摘要事实一致性综述

文章目录 前言0、论文摘要一、Introduction二.背景2.1自动总结任务2.2 数据集DUC-2004Gigaword [Graff et al., 2003, Napoles et al., 2012]CNN/DailyMail [Nallapati 等人&#xff0c;2016]XSum [Narayan 等人&#xff0c;2018] 2.3 摘要系统的评估2.3.1 Rouge [Lin, 2004] 三…

用编程解决习题【计算机图像处理】

用编程解决习题【计算机图像处理】 前言版权第三章 03采样量化与像素间关系作业编程 第六章 06图像的直方图变换作业编程 第七章 07图像的噪声抑制作业编程 第十章 10二值图像的分析作业编程 最后 前言 2023-12-27 21:11:27 以下内容源自《【计算机图像处理】》 仅供学习交流…

CSS3用户界面弹性盒子

CSS3用户界面 resize 该CSS3属性用于定义元素是否应该调整大小&#xff0c;如果需要调整大小&#xff0c;那么以哪个轴进行调整。 语法&#xff1a;resize:both | horizontal | none | vertical 注意&#xff1a;该属性仅应用在overflow值而不是visible的元素上。通常而言&am…

《国货之光》-粗粮八宝粉

国潮正当时&#xff0c;好物当自强。赋能国货品牌&#xff0c;打造行业爆品。今天为大家带来的国货好物是老磨坊纯杂粮八宝粉 小时候&#xff0c;经常听妈妈说&#xff0c;要多吃杂粮&#xff0c;对身体好。然后每天不重样的给我做杂粮饭菜。不知道大家有没有和我相同的经历。…

Windows搭建FTP服务器教学以及计算机端口介绍

目录 一. FTP服务器介绍 FTP服务器是什么意思&#xff1f; 二.Windows Service 2012 搭建FTP服务器 1.开启防火墙 2.创建组 ​编辑3.创建用户 4.用户绑定组 5.安装ftp服务器 ​编辑6.配置ftp服务器 7.配置ftp文件夹的权限 8.连接测试 三.计算机端口介绍 什么是网络…

单集群400TB,OceanBase稳定支撑快手核心业务场景

一款日均超过千万人访问的短视频 App 快手&#xff0c;面对高并发流量如何及时有效地处理用户请求&#xff1f;通过在后端配置多套 MySQL 集群来支撑高流量访问&#xff0c;以解决大数据量存储和性能问题&#xff0c;这种传统的 MySQL 分库分表方案有何问题&#xff1f;快手对分…

c语言结构体数组

使用结构体变量建立数据类型后&#xff0c;我们就可以利用这个数据类型创建数组&#xff0c;就比如创建整形数组就可以写为 int arr[ ],那么创建一个自己建立的数据类型的数组就可以写为&#xff1a; 声明的结构体类型名 数组名。 例如我们声明一个结构体类型struct my后&am…