Java面试——锁

公平锁: 是指多个线程按照申请锁的顺序来获取锁,有点先来后到的意思。在并发环境中,每个线程在获取锁时会先查看此锁维护的队列,如果为空,或者当前线程是等待队列的第一个,就占有锁,否则就会加入到等待队列中,以后会按照 FIFO 的规则从队列中取到自己。

非公平锁: 指多个线程获取锁的顺序并不是按照申请锁的顺序,上来就尝试占有锁,如果尝试失败,就再采用类似公平锁的方式获取锁。有可能后申请的线程比先申请的线程优先获取锁,在高并发的情况下,有可能会造成优先级反转或者饥饿现象。

ReentrantLock:并发包中ReentrantLock的创建可以指定构造函数的boolean类型来得到公平锁或非公平锁,默认是false(非公平锁)。非公平的优点在于吞吐量比公平锁大。对于Synchronized锁也是一种非公平锁。

可重入锁(又名递归锁): 指同一线程外层函数获得锁之后,内层递归函数仍然能获取该锁的代码。也就是说,线程可以进入任何一个它已经拥有的锁,所同步的代码块。synchronizedunlock都是可重入锁。

//简单理解,就是方法1 是一个同步方法,里面包含了一个方法2 也是同步方法,但是当进入方法1后,也就获得了方法2的锁,即可重入锁
public synchronized void method1(){
	System.out.println("方法1 synchronized");
	method2();
}

public synchronized  void method2(){
	System.out.printf("方法2 synchronized");
}

自旋锁: 是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式尝试获取锁,这样的好处是减少了上下文切换的消耗,确定是循环会消耗CPU。循环比较直到成功为止。

public final int getAndAddInt(Object var1, long var2, int var4){
    int var5;
    do{
        //根据对象和地址偏移量获取内存中的值
        var5 = this.getIntVolatile(var1, var2);
    //将获取到的值 var5 传入,此方法内部会先比较var2地址的值是否等于 var5,相等则修改var5值并返回,否则重新进入循环。
    }while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
        return var5;
}

手写一个自旋锁: 思想就是通过while中的循环条件来充当锁,当条件成立时,表示未获得锁,进行死循环,直到while条件不成立,也就是获得锁。就退出死循环,执行业务逻辑。具体查看如下代码:

public class Test {
	public static void main(String[] args) throws Exception {
		/*执行结果展示:  AA   myLock
						BB   myLock
						AA    unLock
						BB    unLock
		 *  分析:我们 AA 线程休眠了 5秒足以让 BB 线程执行结束,那为什么 BB 执行到 myLock 之后就没有继续执行呢。
		 *  其实,BB 一直执行着,只不过陷入了 while 死循环中,因为 AA 将线程置为非空。
		 *  等到 5 秒后,AA unlock 重新将线程=null时,BB 获取线程并执行任务。over
		 */
		Test test = new Test();
		new Thread(()->{
				test.myLock();
				try {
					TimeUnit.SECONDS.sleep(5);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				test.unLock();
			},"AA").start();

			TimeUnit.SECONDS.sleep(1);

			new Thread(()->{
				test.myLock();
				try {
					TimeUnit.SECONDS.sleep(1);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				test.unLock();
			},"BB").start();
	}

	//对线程保证原子性
	AtomicReference<Thread> atomicReference = new AtomicReference<>();
	//获取锁,其实质,将锁看做一个条件判断,只要这个判断能够保证线程安全即可。
	//如下:我们将线程是否为空作为条件,如果是空的就没锁,自己可以对其加锁,将其值设为自己。
	//如果使用完,使用unlock 将线程设置为 null,其他线程通过判断来获得锁,其实就像一种约定而已。
	public void myLock(){
		Thread thread = Thread.currentThread();
		System.out.println(thread.getName()+"   myLock");
		while (!atomicReference.compareAndSet(null,thread)){
		   
		}
	}
	
	//释放锁
	public void unLock(){
		Thread thread = Thread.currentThread();
		atomicReference.compareAndSet(thread,null);
		System.out.println(thread.getName()+"    unLock");
	}
}

自旋锁的优点主要包括:
【1】减少线程阻塞:对于锁竞争不激烈且锁占用时间短暂的情况,自旋锁能够显著提高性能,因为它减少了线程因阻塞而产生的上下文切换开销。
【2】避免内核态切换:与非自旋锁相比,自旋锁在尝试获取锁失败时会继续执行循环而不立即陷入内核态,这样可以避免线程在用户态和内核态之间的频繁切换,这在一定程度上提高了系统的整体性能。

然而,自旋锁也存在一些缺点:
【1】高负载下效率低下:如果锁竞争激烈或持有锁的线程需要长时间执行同步块,自旋锁会因为不断重复无效的旋转操作而导致性能下降。在这种情况下,自旋锁的消耗可能会超过线程阻塞后的恢复成本,因此应该关闭自旋锁以避免不必要的性能损失。1234
【2】可能存在不公平性:某些自旋锁实现(如Java中的)不是完全公平的,这意味着它们可能无法为等待时间最长线程提供优先权,这可能导致所谓的“线程饥饿”问题。
【3】单核处理器上的限制:在单核处理器上,自旋锁实际上没有真正的并行性,因为即使当前线程不阻塞其他线程,锁仍然不会被释放,导致资源的浪费。此外,如果处理器数量少于线程数量,自旋锁也可能造成不必要的资源浪费。4
【4】不适合计算密集型任务:如果任务主要是计算密集型的,使用自旋锁可能会导致性能下降,因为自旋锁会占用CPU资源,而在计算密集型任务中,减少锁的使用可能是更优的选择。

综上所述,自旋锁适用于锁竞争不太激烈且锁占用时间较短的场景,但在竞争激烈或锁占用时间较长的情况下,其性能优势不明显,甚至可能导致性能下降。

【独占锁】(写锁): 指该锁只能被一个线程所持有。对ReentrantLockSynchronized而言都是独占锁。
【共享锁】(读锁): 指该锁可被多个线程持有。

【1】不加读写锁时,代码及出现的问题如下:创建5个线程进行写入,5个线程进行读取。

public class ReadWriteLock {
    private volatile Map map = new HashMap();

    //写入方法
    public void put(String k,Object v){
        System.out.println(Thread.currentThread().getName()+"   开始写入:"+k );
        try {
            TimeUnit.MICROSECONDS.sleep(30);
        }catch (Exception e){
            e.printStackTrace();
        }
        map.put(k,v);
        System.out.println(Thread.currentThread().getName()+"   写入完成");
    }

    //读方法
    public void get(String k){
        System.out.println(Thread.currentThread().getName()+"   读数据开始:"+k );
        try {
            TimeUnit.MICROSECONDS.sleep(10);
        }catch (Exception e){
            e.printStackTrace();
        }
        Object v = map.get(k);
        System.out.println(Thread.currentThread().getName()+"   读数据完场完成"+v);
    }

    public static void main(String[] args) {
        ReadWriteLock readWriteLock = new ReadWriteLock();

        //写入数据
        for (int i=1;i<6;i++){
            final int tempInt = i;
            new Thread(()->{
                readWriteLock.put(tempInt+"",tempInt+"");
            },String.valueOf(i)).start();
        }

        //读取数据
        for(int i=1;i<6;i++){
            final int tempInt = i;
            new Thread(()->{
                readWriteLock.get(tempInt+"");
            },String.valueOf(i)).start();
        }
    }
}

【2】上述代码输出如下:第一个线程未写入完成时,其他线程就进入了该方法,进行了写操作。不符合多线程安全特性。

在这里插入图片描述

【3】加入读写锁:ReentrantReadWriteLock(读写锁)位于JUC包下

public class ReadWriteLock{
    private volatile Map map = new HashMap();
    private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    //写入方法
    public void put(String k,Object v){
        rwLock.writeLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"   开始写入:"+k );
            try {
                TimeUnit.MICROSECONDS.sleep(30);
            }catch (Exception e){
                e.printStackTrace();
            }
            map.put(k,v);
            System.out.println(Thread.currentThread().getName()+"   写入完成");
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            rwLock.writeLock().unlock();
        }
    }

    //读方法
    public void get(String k){
        rwLock.readLock().lock();
        try {
            System.out.println(Thread.currentThread().getName()+"   读数据开始:"+k );
            try {
                TimeUnit.MICROSECONDS.sleep(10);
            }catch (Exception e){
                e.printStackTrace();
            }
            Object v = map.get(k);
            System.out.println(Thread.currentThread().getName()+"   读数据完场完成"+v);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            rwLock.readLock().unlock();
        }
    }

    public static void main(String[] args) {
        ReadWriteLock readWriteLock = new ReadWriteLock();
        //写入数据
        for (int i=1;i<6;i++){
            final int tempInt = i;
            new Thread(()->{
                readWriteLock.put(tempInt+"",tempInt+"");
            },String.valueOf(i)).start();
        }

        //读取数据
        for(int i=1;i<6;i++){
            final int tempInt = i;
            new Thread(()->{
                readWriteLock.get(tempInt+"");
            },String.valueOf(i)).start();
        }
    }
}

【4】加入读写锁后,输出如下:
在这里插入图片描述

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

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

相关文章

7zip压缩工具的Linux命令

Macos系统自带的压缩工具&#xff0c;压缩效果一点都不好&#xff0c;压缩效果不明显&#xff0c;果断用7ZIP软件&#xff0c;但是在mac系统下&#xff0c;7zip是没有可视化界面的&#xff0c;只有通过命令是操作。 1 安装方式 &#xff08;1&#xff09;源码安装 https://ww…

vuex配置和使用(vue3配置)

个人理解可能会有所偏差 1、基础使用 首先在创建项目时可以选择vuex和一些其他的配置&#xff0c;如果选择那么他会自动创建store文件夹生成默认格式&#xff0c;如果没有选择可以使用指令&#xff1a; npm install vuexnext --save 然后手动创建即可 import { createStore }…

SpringBoot 学习笔记

文章目录 一、IoC二、AOP三、bean3.1 bean 生命周期3.2 三种依赖注入方式3.3 bean 线程安全 四、SpringMVC五、常用注解5.1 Scope5.2 PostConstruct 和 PreDestroy5.3 Component 和 Bean5.4 Autowired 和 Resource 六、基于 ApplicationContextAware 实现工厂模式七、事务失效八…

docker创建mongodb数据库容器

介绍 本文将通过docker创建一个mongodb数据库容器 1. 拉取mongo镜像 docker pull mongo:3.63.6版本是一个稳定的版本&#xff0c;可以选择安装此版本。 2. 创建并启动主数据库 容器数据卷配置 /docker/mongodb/master/data # 数据库数据目录&#xff08;宿主机&am…

下载huggingface数据集到本地并读取.arrow文件遇到的问题

文章目录 1. 524MB中文维基百科语料&#xff08;需要下载的数据集&#xff09;2. 下载 hugging face 网站上的数据集3. 读取 .arrow 文件报错代码4. 纠正后代码 1. 524MB中文维基百科语料&#xff08;需要下载的数据集&#xff09; 2. 下载 hugging face 网站上的数据集 要将H…

springboot+vue前后端分离适配cas认证的跨域问题

0. cas服务搭建参考:CAS 5.3服务器搭建_cas-overlay-CSDN博客 1. 参照springsecurity适配cas的方式, 一直失败, 无奈关闭springssecurity认证 2. 后端服务适配cas: 参考前后端分离项目(springbootvue)接入单点登录cas_前后端分离做cas单点登录-CSDN博客 1) 引入maven依赖 …

Word页码怎么设置?6个提升效率好方法!

“我刚刚编辑完一个Word文档&#xff0c;想给它加上页码&#xff0c;但是我还不知道应该怎么操作。大家平常是怎么给Word设置页码的呢&#xff1f;” 在使用Word编辑文档时&#xff0c;页码的设置是一个常见的需求。无论是为了方便阅读&#xff0c;还是为了符合特定的格式要求&…

亿道丨三防平板丨如何从多方面选择合适的三防加固平板?

在如今这个信息爆炸的时代&#xff0c;移动设备已经成为我们生活和工作的必备工具。然而&#xff0c;在一些特殊的场合中&#xff0c;普通的平板电脑可能无法满足需求&#xff0c;比如工厂车间、野外作业、极端天气等环境下。此时&#xff0c;三防平板就成了不二之选。那么&…

怎么免费找回误删文件?这5个数据恢复工具能救你一命

如果不小心删除了文件&#xff0c;不要慌张&#xff0c;今天这个视频将为大家推荐5个最好的文件恢复软件。 误删文件是很多人在日常生活中都会遇到的问题&#xff0c;而找回丢失的数据更是至关重要。现在&#xff0c;有许多文件恢复软件可以帮助您快速找回丢失的重要文件。这些…

flutter sliver 多种滚动组合开发指南

flutter sliver 多种滚动组合开发指南 视频 https://youtu.be/4mho1kZ_YQU https://www.bilibili.com/video/BV1WW4y1d7ZC/ 前言 有不少同学工作中遇到需要把几个不同滚动行为组件&#xff08;顶部 appBar、内容固定块、tabBar 切换、tabBarView视图、自适应高度、横向滚动&a…

【前端素材】推荐优质后台管理系统Uena平台模板(附源码)

一、需求分析 后台管理系统&#xff08;或称作管理后台、管理系统、后台管理平台&#xff09;是一种专门用于管理网站、应用程序或系统后台运营的软件系统。它通常由一系列功能模块组成&#xff0c;为管理员提供了管理、监控和控制网站或应用程序的各个方面的工具和界面。以下…

sonar-java 手写一个规则-单元测试分析

前言 最近做项目&#xff0c;定制sonar规则&#xff0c;提高Java代码质量&#xff0c;在编写的sonar规则&#xff0c;做验证时&#xff0c;使用单元测试有一些简单的心得感悟&#xff0c;分享出来。 自定义规则模式 sonar的自定义规则很简单&#xff0c;一般而言有2种模式可…

基于自适应波束成形算法的matlab性能仿真,对比SG和RLS两种方法

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于自适应波束成形算法的matlab性能仿真,对比SG和RLS两种方法. 2.测试软件版本以及运行结果展示 MATLAB2022a版本运行 3.核心程序 ........................…

智慧农业之农产品溯源管理

总体设计 系统采用RFID无线射频识别技术对产品的生产、加工、运输、包装、配送等各个环节实施全程监控与可追溯。利用射频识别(RFID)、红外感应器、激光扫描器、传感器等信息传感设备,把任何物品与互联网连接起来,进行信息交换和通讯,以实现智能化识别、定位、跟踪、监控…

stable diffusion学习笔记 手部修复

图片手部修复原理 某张图片在生成后&#xff0c;仅有手部表现不符合预期&#xff08;多指&#xff0c;畸形等&#xff09;。这种情况下我们通常使用【局部重绘】的方式对该图片的手部进行【图生图】操作&#xff0c;重新绘制手部区域。 但是仅采用重绘的方式也很难保证生成的…

Redis 分布式锁

什么是分布式锁 在一个分布式的系统中&#xff0c;也会涉及到多个节点访问同一个公共资源的情况。此时就需要通过锁来做互斥控制&#xff0c;避免出现类似于“线程安全”的问题。 而 java 的 synchronized 或者 C 的 std::mutex&#xff0c;这样的锁都是只能在当前进程中生效…

leetcode-hot100-双指针

剪枝&#xff0c;减少不必要的计算 283. 移动零 示例 1: 输入: nums [0,1,0,3,12] 输出: [1,3,12,0,0] 示例 2: 输入: nums [0] 输出: [0] 请注意 &#xff0c;必须在不复制数组的情况下原地对数组进行操作。 第一印象&#xff1a;使用一个辅助数组&#xff0c;同时以…

el-submenu is-opened 展开/闭合;el-submenu is-opened保持一个子菜单的展开控制

写了个mes系统目录 点击子菜单展开后&#xff0c;上一级菜单没有默认关闭。主流后台管理系统大部分都是保持一个子菜单关闭状态、 问度娘无果后&#xff0c;查询官网&#xff0c;一个属性搞定。 unique-opened 是否只保持一个子菜单的展开 加在 <el-menu 组件上即可 完整代…

react中修改state中的值无效?

// 初始化state state {personArr:[{name:张三,id:1},{name:李四,id:2},{name:王五,id:3}] }componentDidMount(){const newName 赵六const indexUpdate 1const newArr this.state.personArr.map((item,index)>{if(indexUpdate index){return {...item,name:newName}}e…

Java 过滤器深入了解学习

Java 过滤器深入了解学习 生活不能等待别人来安排&#xff0c;要自己去争取和奋斗&#xff1b;而不论其结果是喜是悲&#xff0c;但可以慰藉的是&#xff0c;你总不枉在这世界上活了一场。有了这样的认识&#xff0c;你就会珍重生活&#xff0c;而不会玩世不恭&#xff1b;同时…