当前位置: 首页 > news >正文

悲观的并发策略——synchronized互斥锁

互斥锁是最常见的同步手段,在并发过程中,当多条线程对同一个共享数据竞争时,它保证共享数据同一时刻只能被一条线程使用,其他线程只有等到锁释放后才能重新进行竞争。

对于Java开发人员,最熟悉的肯定就是用synchronized关键词完成锁功能,在涉及到多线程并发时,对于一些变量,你应该会毫不犹豫地加上synchronized去保证变量的同步性。

在C/C++中可直接使用操作系统提供的互斥锁实现同步和线程的阻塞和唤起,与之不同的是,Java要把这些底层封装,而synchronized就是一个典型的互斥锁,同时它也是一个JVM级别的锁,它的实现细节全部封装在JVM中实现,对开发人员只提供了synchronized关键词。

根据锁的颗粒度,可以用synchronized对一个变量、一个方法、一个对象和一个类等加锁。被synchronized修饰的程序块经过编译后,会在前后生成monitorenter和monitorexit两个字节码指令,其中涉及到锁定和解锁对象的确定,这就要根据synchronized来确定了,假如明确指定了所对象,例如synchronized(变量)、synchronized(this)等,说明加解锁对象为变量或运行时对象。假如没有明确指定对象,则根据synchronized修饰的方法去找对应的锁对象,如修饰一个非静态方法表示此方法对应的对象为锁对象,如修饰一个静态方法则表示此方法对应的类对象为锁对象。当一个对象被锁住时,对象里面所有用synchronized修饰的方法都将产生堵塞,而对象里非synchronized修饰的方法可正常被调用,不受锁影响。

为了实现互斥锁,JVM的monitorenter和monitorexit字节码依赖底层操作系统的互斥锁来实现,Java层面的线程与操作系统的原生线程有映射关系,这时如果要将一个线程进行阻塞或唤起都需要操作系统的协助,需要从用户态切换到内核态来执行,这种切换代价十分昂贵,需要消耗很多处理器时间。如果可能,应该减少这样的切换,JVM一般会采取一些措施进行优化,例如在把线程进行阻塞操作之前先让线程自旋等待一段时间,可能在等待期间其他线程已经解锁,这时就无需再让线程执行阻塞操作,避免了用户态到内核态的切换。

Synchronized还有另外一个重要的特性——可重入性。这个特性主要是针对当前线程而言的,可重入即是自己可以再次获得自己的内部锁,在尝试获取对象锁时,如果当前线程已经拥有了此对象的锁,则把锁的计数器加一,在释放锁时则对应地减一,当锁计数器为0时表示锁完全被释放,此时其他线程可对其加锁。可重入特性是为了解决自己锁死自己的情况,如下面伪代码:

public class DeadLock{
    public synchronized void method1(){}
    public synchronized void method2(){
        this.method1();
    }
    public static void main(String[] args){
        DeadLock deadLock=new DeadLock();
        deadLock.method2();
    }
}
复制代码

这种情况其实也并非不常见,一个类中的同步方法调用另一个同步方法,假如synchronized不支持重入,进入method2方法时当前线程将尝试获取deadLock对象的锁,而method2方法里面执行method1方法时,当前线程又要去尝试获取deadLock对象的锁,这时由于不支持重入,它要去等deadLock对象的锁释放,把自己阻塞了,这就是自己锁死自己的现象。所以重入机制的引入,杜绝了这种情况的发生。

synchronized实现的是一个非公平锁,非公平主要表现在获取锁的行为上,并非是按照申请锁的时间前后给等待线程分配锁的,每当锁被释放后,任何一个线程都有机会竞争到锁,这样做的目的是为了提高执行性能,当然也会产生线程饥饿现象。

synchronized最后一个特性(缺点)就是不可中断性,在所有等待的线程中,你们唯一能做的就是等,而实际情况可能是有些任务等了足够久了,我要取消此任务去干别的事情,此时synchronized是无法帮你实现的,它把所有实现机制都交给了JVM,提供了方便的同时也体现出了自己的局限性。

-------------推荐阅读------------

我的2017文章汇总——机器学习篇

我的2017文章汇总——Java及中间件

我的2017文章汇总——深度学习篇

我的2017文章汇总——JDK源码篇

我的2017文章汇总——自然语言处理篇

我的2017文章汇总——Java并发篇

------------------广告时间----------------

跟我交流,向我提问:

公众号的菜单已分为“分布式”、“机器学习”、“深度学习”、“NLP”、“Java深度”、“Java并发核心”、“JDK源码”、“Tomcat内核”等,可能有一款适合你的胃口。

为什么写《Tomcat内核设计剖析》

欢迎关注:

相关文章:

  • 一步一步构建自己的管理系统之 ------登入注册
  • 64位linux安装wine等软件
  • javascript 日期函数
  • spring IOC AOP实现原理
  • Python数据挖掘与机器学习技术入门实战
  • 万达酒店及度假村首创微信智能开票辅助功能
  • 【vue】vue +element 搭建项目,vuex中的store使用
  • 面向对象进阶------模块 json pickle hashlib
  • ApiLeaf·可能是史上最省事的文档生成工具
  • CSDN日报20170226——《你离心想事成仅仅差一个计划》
  • 云服务能力评估“国标”出炉,新华三首批通过增强级认证
  • 会话管理(session)
  • 在Office应用中打开WPF窗体并且让子窗体显示在Office应用上
  • 分享一款本地音乐播放器源码
  • Swoft 源码剖析 - 代码自动更新机制
  • 【140天】尚学堂高淇Java300集视频精华笔记(86-87)
  • Apache的80端口被占用以及访问时报错403
  • CSS中外联样式表代表的含义
  • linux安装openssl、swoole等扩展的具体步骤
  • nodejs:开发并发布一个nodejs包
  • overflow: hidden IE7无效
  • Rancher-k8s加速安装文档
  • spring-boot List转Page
  • 闭包--闭包作用之保存(一)
  • 程序员最讨厌的9句话,你可有补充?
  • 聊聊springcloud的EurekaClientAutoConfiguration
  • 我看到的前端
  • 远离DoS攻击 Windows Server 2016发布DNS政策
  • 在weex里面使用chart图表
  • 看到一个关于网页设计的文章分享过来!大家看看!
  • ​人工智能之父图灵诞辰纪念日,一起来看最受读者欢迎的AI技术好书
  • ###STL(标准模板库)
  • (cljs/run-at (JSVM. :browser) 搭建刚好可用的开发环境!)
  • (Redis使用系列) Springboot 实现Redis 同数据源动态切换db 八
  • (ZT)北大教授朱青生给学生的一封信:大学,更是一个科学的保证
  • (附程序)AD采集中的10种经典软件滤波程序优缺点分析
  • (循环依赖问题)学习spring的第九天
  • (转载)虚函数剖析
  • 、写入Shellcode到注册表上线
  • .net core 调用c dll_用C++生成一个简单的DLL文件VS2008
  • .NET Core 和 .NET Framework 中的 MEF2
  • .NET Core中的去虚
  • .NET 使用 ILMerge 合并多个程序集,避免引入额外的依赖
  • .NET大文件上传知识整理
  • .NET开发人员必知的八个网站
  • @RestController注解的使用
  • [ C++ ] 继承
  • [].slice.call()将类数组转化为真正的数组
  • [100天算法】-每个元音包含偶数次的最长子字符串(day 53)
  • [AIGC codze] Kafka 的 rebalance 机制
  • [BZOJ2208][Jsoi2010]连通数
  • [C puzzle book] types
  • [CareerCup] 6.1 Find Heavy Bottle 寻找重瓶子
  • [delphi]保证程序只运行一个实例
  • [java进阶]——方法引用改写Lambda表达式