锁机制-程序员宅基地

技术标签: 操作系统  

参考链接

常见的锁机制

Java中常用的锁机制

浅谈Java锁机制

java锁机制

不可不说的Java“锁”事

锁机制:一种保护机制,在多线程的情况下,保证操作数据的正确性 / 一致性

Synchronized 锁【S】

  • synchronized 机制是 给共享资源上锁只有拿到锁的线程才可以访问共享资源,这样就可以强制使得对共享资源的访问都是顺序的
  • Synchronized 是Java 关键字,属于Java 的内置特性
  • 基于 JVM 来保证数据同步
  • 无需手动释放锁,会一直等待

Lock锁【L】

  • 硬件层面 ,依赖特殊的CPU指令实现数据同步的
  • ReentrantLock:默认非公平但可实现公平的
  • 需要手动释放锁,可中断线程

线程是否要锁住同步资源

在这里插入图片描述

悲观锁【S、RL】

悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改

悲观锁在Java中的使用,就是利用 各种锁

  • 悲观锁 适合写操作多 的场景,先加锁可以保证写操作时数据正确
  • 重量级锁是悲观锁的一种
  • 悲观锁基本都是在显式的锁定之后再操作同步资源

	// synchronized
	public synchronized void testMethod() {
    
		// 操作同步资源
	}
	
	// ReentrantLock
	private ReentrantLock lock = new ReentrantLock(); // 需要保证多个线程使用的是同一个锁
	public void modifyPublicResources() {
    
		lock.lock();
		// 操作同步资源
		lock.unlock();
	}

乐观锁

乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作(例如报错或者自动重试)

乐观锁在Java中的使用,是 无锁编程,常常采用的是 CAS算法

【这使得乐观锁能够做到不锁定同步资源也可以正确的实现线程同步】

中转站:CAS 无锁机制

  • 典型的例子就是Java原子类中的递增操作就通过CAS自旋实现的
  • 乐观锁 适合读操作多 的场景,不加锁的特点能够使其读操作的性能大幅提升
  • 自旋锁、轻量级锁与偏向锁属于乐观锁
  • 乐观锁则直接去操作同步资源
	// 需要保证多个线程使用的是同一个AtomicInteger
	private AtomicInteger atomicInteger = new AtomicInteger();  
	
	//执行自增1
	atomicInteger.incrementAndGet(); 

多个线程竞争锁流程(锁的状态)【S】

前提知识:

Java对象头

synchronized是悲观锁,在操作同步资源之前需要给同步资源先加锁,这把锁就是存在Java对象头里的

Monitor

  • 可以理解为一个同步工具或一种同步机制,通常被描述为一个对象,每一个Java对象就有一把看不见的锁,称为内部锁或者Monitor锁
  • Monitor是线程私有的数据结构,每一个线程都有一个可用monitor record列表,同时还有一个全局的可用列表
  • 每一个被锁住的对象都会和一个monitor关联,同时monitor中有一个Owner字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用

无锁状态

  • 无锁没有对资源进行锁定,所有的线程都能访问并修改同一个资源,但同时只有一个线程能修改成功
  • CAS原理及应用即是无锁的实现

以下三种锁是指锁的 状态,并且是 针对 Synchronized,通过对象监视器在对象头中的字段来表明的

synchronized通过Monitor来实现线程同步

Monitor是依赖于底层的操作系统的Mutex Lock(互斥锁)来实现的线程同步

偏向锁 通过对比Mark Word解决加锁问题,避免执行CAS操作

轻量级锁 通过用CAS操作和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能

重量级锁 将除了拥有锁的线程以外的线程都阻塞

偏向锁

一段同步代码一直被一个线程所访问,那么该线程会 自动获取锁,降低获取锁的代价

轻量级锁

当锁是 偏向锁 的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能

重量级锁

  • 当锁为 轻量级锁 的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁,重量级锁会让其他申请的线程进入 阻塞,性能降低
  • 依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”

获取锁失败,线程是否堵塞

前提知识:

  • 阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间
  • 如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长
  • 在许多场景中,同步资源的锁定时间很短,为了这一小段时间去切换线程,线程挂起和恢复现场的花费可能会让系统得不偿失
  • 如果物理机器有多个处理器,能够让两个或以上的线程同时并行执行,我们就可以让后面那个请求锁的线程不放弃CPU的执行时间,看看持有锁的线程是否很快就会释放锁

在这里插入图片描述

自旋锁

中转站:

自旋锁

尝试获取锁的线程不会立即阻塞(尽可能的减少线程的阻塞),而是采用循环的方式去 尝试获取锁

实现原理

  • CAS

好处

  • 避免切换线程的开销

缺点

  • 循环会 消耗CPU,占用了处理器的时间
  • 自旋锁在获取锁前一直都是占用cpu做无用功,同时有大量线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗,其它需要cpu的线程又不能获取到cpu,造成cpu的浪费

适用

  • 锁的 竞争不激烈,且 占用锁时间非常短 的代码块来说性能能大幅度的提升
  • 因为自旋的消耗会小于线程阻塞挂起再唤醒的操作的消耗

非自旋

多线程竞争锁时是否需要排队

公平锁

多个线程按照 申请锁的顺序 来获取锁

优点

  • 等待锁的线程不会饿死

缺点

  • 整体吞吐效率相对非公平锁要低,等待队列中除第一个线程以外的所有线程都会阻塞
  • CPU唤醒阻塞线程的开销比非公平锁大

非公平锁【S、RL】

多个线程获取锁的顺序 并不是按照申请锁的顺序,多个线程加锁时直接尝试获取锁,获取不到才会到等待队列的队尾等待

优点

  • 可以减少唤起线程的开销,整体的吞吐效率高,因为线程有几率不阻塞直接获得锁,CPU不必唤醒所有线程

缺点

  • 处于等待队列中的线程可能会饿死,或者等很久才会获得锁

一个线程多个流程能否获得相同锁

可重入锁【S 、RL】

究竟什么是可重入锁?

”递归锁“:广义上的可重入锁指 可重复可递归调用 的锁,在外层使用锁之后,在内层仍然可以使用(前提得是同一个对象或者class)


	public class Widget {
    
	    public synchronized void doSomething() {
    
	        System.out.println("方法1执行...");
	        doOthers();
	    }
	
	    public synchronized void doOthers() {
    
	        System.out.println("方法2执行...");
	    }
	}

在上面的代码中,类中的两个方法都是被内置锁synchronized修饰的,doSomething()方法中调用doOthers()方法。因为内置锁是可重入的,所以同一个线程在调用doOthers()时可以直接获得当前对象的锁,进入doOthers()进行操作。

如果是一个不可重入锁,那么当前线程在调用doOthers()之前需要将执行doSomething()时获取当前对象的锁释放掉,实际上该对象锁已被当前线程所持有,且无法释放。所以此时会出现死锁

优点

  • 一定程度避免死锁

实现原理

通过重入锁ReentrantLock以及非可重入锁NonReentrantLock的源码来对比分析死锁产生原因

  • 首先ReentrantLock和NonReentrantLock都继承父类AQS,其父类AQS中维护了一个同步状态status来计数重入次数,status初始值为0

  • 当线程尝试获取锁时

    • 可重入锁先尝试获取并更新status值,如果status == 0表示没有其他线程在执行同步代码,则把status置为1,当前线程开始执行。如果status != 0,则判断当前线程是否是获取到这个锁的线程,如果是的话执行status+1,且当前线程可以再次获取锁
    • 非可重入锁是直接去获取并尝试更新当前status的值,如果status != 0的话会导致其获取锁失败,当前线程阻塞
  • 释放锁时

    • 可重入锁同样先获取当前status的值,在当前线程是持有锁的线程的前提下。如果status-1 == 0,则表示当前线程所有重复获取锁的操作都已经执行完毕,然后该线程才会真正释放锁
    • 非可重入锁则是在确定当前线程是持有锁的线程之后,直接将status置为0,将锁释放

多个线程能否共享相同锁

独享锁 / 共享锁这是广义上的说法,互斥锁 / 读写锁就分别对应具体的实现

独享锁与共享锁是 通过AQS 来实现的

锁升级:读锁到写锁 (不支持)

锁降级:写锁到读锁 (支持)

【通过ReentrantLock(互斥锁)和ReentrantReadWriteLock(读写锁)介绍独享锁和共享锁】

独享锁(互斥锁)【S、RL】

一次只能被 一个线程 所持有

共享锁(读写锁)

可被 多个线程 所持有

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_42959073/article/details/109866764

智能推荐

基于内核4.19版本的XFRM框架_linux的xfrm框架-程序员宅基地

文章浏览阅读794次,点赞2次,收藏5次。XFRM框架_linux的xfrm框架

织梦常用标签整理_织梦中什么页面用什么标签教学-程序员宅基地

文章浏览阅读774次。DedeCMS常用标签讲解笔记整理 今天我们主要将模板相关内容,在前面的几节课中已经基本介绍过模板标签的相关内容,大家可以下载天工开物老师的讲课记录:http://bbs.dedecms.com/132951.html,这次课程我们主要讲解模板具体的标签使用,并且结合一些实例来介绍这些标签。 先前课程介绍了,网站的模板就如同一件衣服,衣服的好坏直接决定了网站的好坏,很多网站一看界面_织梦中什么页面用什么标签教学

工作中如何编译开源工具(gdb)_gdb编译-程序员宅基地

文章浏览阅读2.5k次,点赞2次,收藏15次。编译是大部分工程师的烦恼,大家普遍喜欢去写业务代码。但我觉得基本的编译流程,我们还是需要掌握的,希望遇到相关问题,不要退缩,尝试去解决。天下文章一大抄,百度能解决我们90%的问题。_gdb编译

python简易爬虫v1.0-程序员宅基地

文章浏览阅读1.8k次,点赞4次,收藏6次。python简易爬虫v1.0作者:William Ma (the_CoderWM)进阶python的首秀,大部分童鞋肯定是做个简单的爬虫吧,众所周知,爬虫需要各种各样的第三方库,例如scrapy, bs4, requests, urllib3等等。此处,我们先从最简单的爬虫开始。首先,我们需要安装两个第三方库:requests和bs4。在cmd中输入以下代码:pip install requestspip install bs4等安装成功后,就可以进入pycharm来写爬虫了。爬

安装flask后vim出现:error detected while processing /home/zww/.vim/ftplugin/python/pyflakes.vim:line 28_freetorn.vim-程序员宅基地

文章浏览阅读2.6k次。解决方法:解决方法可以去github重新下载一个pyflakes.vim。执行如下命令git clone --recursive git://github.com/kevinw/pyflakes-vim.git然后进入git克降目录,./pyflakes-vim/ftplugin,通过如下命令将python目录下的所有文件复制到~/.vim/ftplugin目录下即可。cp -R ...._freetorn.vim

HIT CSAPP大作业:程序人生—Hello‘s P2P-程序员宅基地

文章浏览阅读210次,点赞7次,收藏3次。本文简述了hello.c源程序的预处理、编译、汇编、链接和运行的主要过程,以及hello程序的进程管理、存储管理与I/O管理,通过hello.c这一程序周期的描述,对程序的编译、加载、运行有了初步的了解。_hit csapp

随便推点

挑战安卓和iOS!刚刚,华为官宣鸿蒙手机版,P40搭载演示曝光!高管现场表态:我们准备好了...-程序员宅基地

文章浏览阅读472次。点击上方 "程序员小乐"关注,星标或置顶一起成长后台回复“大礼包”有惊喜礼包!关注订阅号「程序员小乐」,收看更多精彩内容每日英文Sometimes you play a..._挑战安卓和ios!华为官宣鸿蒙手机版,p40搭载演示曝光!高管表态:我们准备好了

精选了20个Python实战项目(附源码),拿走就用!-程序员宅基地

文章浏览阅读3.8w次,点赞107次,收藏993次。点击上方“Python爬虫与数据挖掘”,进行关注回复“书籍”即可获赠Python从入门到进阶共10本电子书今日鸡汤昔闻洞庭水,今上岳阳楼。大家好,我是小F。Python是目前最好的编程语言之一。由于其可读性和对初学者的友好性,已被广泛使用。那么要想学会并掌握Python,可以实战的练习项目是必不可少的。接下来,我将给大家介绍20个非常实用的Python项目,帮助大家更好的..._python项目

android在线图标生成工具,图标在线生成工具Android Asset Studio的使用-程序员宅基地

文章浏览阅读1.3k次。在网站的导航资源里看到了一个非常好用的东西:Android Asset Studio,可以在线生成各种图标。之前一直在用一个叫做Android Icon Creator的插件,可以直接在Android Studio的插件里搜索,这个工具的优点是可以生成适应各种分辨率的一套图标,有好几种风格的图标资源,遗憾的是虽然有很多套图标风格,毕竟是有限的。Android Asset Studio可以自己选择其..._在线 android 图标

android 无限轮播的广告位_轮播广告位-程序员宅基地

文章浏览阅读514次。无限轮播广告位没有录屏,将就将就着看,效果就是这样主要代码KsBanner.java/** * 广告位 * * Created by on 2016/12/20. */public class KsBanner extends FrameLayout implements ViewPager.OnPageChangeListener { private List

echart省会流向图(物流运输、地图)_java+echart地图+物流跟踪-程序员宅基地

文章浏览阅读2.2k次,点赞2次,收藏6次。继续上次的echart博客,由于省会流向图是从echart画廊中直接取来的。所以直接上代码<!DOCTYPE html><html><head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" /&_java+echart地图+物流跟踪

Ceph源码解析:读写流程_ceph 发送数据到其他副本的源码-程序员宅基地

文章浏览阅读1.4k次。一、OSD模块简介1.1 消息封装:在OSD上发送和接收信息。cluster_messenger -与其它OSDs和monitors沟通client_messenger -与客户端沟通1.2 消息调度:Dispatcher类,主要负责消息分类1.3 工作队列:1.3.1 OpWQ: 处理ops(从客户端)和sub ops(从其他的OSD)。运行在op_tp线程池。1...._ceph 发送数据到其他副本的源码