描述

关于synchronized

众所周知,JAVA中最简单的加锁方法是用关键字synchronized,我们可以使用这个关键字将一个方法变成线程安全的,也可以将一个代码块变成线程安全的,这样子我们不需要再担心多线程同时执行到这段代码会引发的并发问题。同时配合方法wait,notify和notifyall可以很好的实现多线程之间的协作,比如某个线程因为需要等待一些资源,于是调用wait方法将自己设置为waiting状态,其他线程释放或生产这个线程需要的资源的时候需要通知这个线程(notify)将其唤醒,或者通知所有等待当前资源的线程(notifyall)。

然而当功能完成之后我们似乎并不满足于此,于是我们开始考虑这么做的代价是什么,是否可以做的更好。

先说说这么做(使用synchronized)的代价是什么,当多个线程请求临界资源的时候只能有一个线程得到满足,那么其他的线程会做什么呢,他们会被阻塞,直到被通知(notify/notifyall)又有资源的时候才被唤醒进行再一次的锁争用,而后往复的是又只有一个线程能被得到满足,其他的线程继续进入阻塞状态,而这个时候可能会有不断的增加争用线程。性能损耗的关键点在于线程的阻塞操作是由操作系统来完成的,在Linux系统下是由pthread_mutex_lock函数来完成。线程被阻塞之后便进入了内核调度态,这个过程发生了操作系统将保存用户态的上下文进入内核态,这也就是常说的上下文切换,上下文切换代价大,在于操作系统需要将当前线程执行上下文内容(包括堆栈、寄存器等存储的内容)的保存以便之后线程切换回来时候再进行现场恢复。

上面可以看出使用synchronized的代价是什么了吧,当竞争激烈的时候会引起频繁的操作系统上下文切换,从而影响系统的性能。下面再来讲讲自旋锁。

自旋锁的原理

自旋锁是对线程阻塞的一种优化,他的原理简单的说就是当线程争用锁失败的时候不立即进入阻塞状态,而是再等一会,因为对于执行时间短的代码这一会可能就会释放锁,而线程就不需要进行一次阻塞与唤醒。等待操作就是让线程多执行几个空指令,至于等待多久这跟具体的处理器实现有关,也有可能处理器根本不支持自旋锁,具体实现的时候我们可以设置一个临界值,当超过了这个临界值之后我们就不自旋了,就乖乖进入阻塞状态吧。这种优化对于执行时间短的代码是很有效的。synchronized使用自旋锁的时机是线程进入等待队列即阻塞的前一步。

关于偏向锁

偏向锁是java6提供的一种功能,主要是对无竞争条件下的对加锁代码执行的优化,得到优化的地方是省去了对等待队列的更新操作。在竞争条件下,获取锁失败的线程会被放入等待队列,这个队列的更新操作是通过CAS指令来完成的。对于那么一段本部应该被加锁的代码被加了锁,我们认为每次执行这段被加了锁的代码的时候更新等待队列的操作并不是必要的,而CAS操作会延迟本地代码的执行,因此偏向锁是用于优化这个问题的。

关于Lock

Lock是JAVA5增加的内容,在JCU(java.util.concurrent.locks)包下面,作者是并发大师Doug Lea。JCU包提供了很多封装的锁,包括常用的ReentrantLock和ReadWriteLock。这些所其实都是依赖java.util.concurrent.AbstractQueuedSynchronizer这个类来实现的,这个类有个简写的名字叫AQS,对这就是著名的AQS。

关于Lock,先说说线程获取Lock锁的时候会引起哪些事件呢。首先AQS是依赖一个被volatile修饰的int变量来标识当前锁的状态的,为0的时候代表当前锁不被任何线程拥有,当线程拿到这个锁的时候会通过CAS操作修改state的状态,那么对于争用失败的线程AQS会怎么办呢,AQS内部维护了一个等待队列,这个队列是纯JAVA实现的,其实现也是非常巧妙的,多线程在通过CAS来获取自己在队列中的位置,同时队列中的线程状态也是阻塞状态,遇到阻塞就头疼了,上面已经介绍过阻塞会带来的性能问题。在源码中我们可以看到的是AQS通过LockSupport(LockSupport底层依赖Unsafe)将线程阻塞,关于LockSupport我有一篇文章介绍的,其功能是用来代替wait和notity/notifyall的,更好的地方是LockSupport对park方法和unpark方法的调用没有先后的限制,而notify/notifyall必须在wait调用之后调用。尽管如此,这一切并没有阻止线程进入阻塞状态,我有点失望。

无锁时代

讲到无锁,必然是Disruptor并发框架,Disruptor底层依赖一个RingBuffer来进行线程之间的数据交换,无锁在于在并发条件下,多线程对RingBuffer的读和写不会涉及到锁,然而因为RingBuffer满或者RingBuffer中没有可消费内容引发的线程等待,那就要另当别论了。简单几句介绍下无锁原理,RingBuffer维护者可读和可写的指针,也叫游标,它指向生产者或消费者需要写或读的位置,而对于指针的更新是由CAS来完成的,这个过程中我们不需要加锁/解锁的过程。

后记:

JAVA锁方面的知识主要是要搞清楚不同的锁的优点与缺点,深入到操作系统层的实现机制与不同场景中对应用的性能影响。本文简单的撸了一下JAVA锁从synchronized到无锁的发展以及一些锁的简单原理,主要是抛砖引玉吧,因为介绍的比较简单,对于文中提到的知识不知道的同学可以深入了解,我相信你会很有收获。有些实现的原理介绍可能就一句话,但是实际实现起来是蛮复杂的,需要考虑到的东西是我们没有写过所不能考虑到的。到这里,如果你的项目中用到了多线程并发,你是否会考虑使用无锁模型来优化你项目中多线程之间的通信呢。

打开APP精彩内容

点击阅读全文

java加锁_JAVA最好的加锁方法是什么相关推荐

  1. 在java中怎么加锁_JAVA最好的加锁方法是什么

    关于synchronized 众所周知,JAVA中最简单的加锁方法是用关键字synchronized,我们可以使用这个关键字将一个方法变成线程安全的,也可以将一个代码块变成线程安全的,这样子我们不需要 ...

  2. java 耗时_Java使用简单的方法计算代码耗时

    前言:该博客主要是记录自己学习的过程,方便以后查看,当然也希望能够帮到大家. 说明 在我们的实际开发中,多多少少会遇到统计一段代码片段的耗时的情况,下面分享本人常用的方法. 第一步,在pom.xml加 ...

  3. java 复合_Java复合语句的使用方法详解

    与 C 语言及其他语言相同, Java 语言的复合语句是以整个块区为单位的语句,所以又称为块语句.下面我们来看看有关复合语句的使用方法和实例. 复合语句由开括号"{"开始,闭括号& ...

  4. java程序设计_JAVA基础程序设计之方法

    1 基本概念 Java 方法是语句的集合,它们在一起执行一个功能. l 方法是解决一类问题的步骤的有序组合 l 方法包含于类或对象中 l 方法在程序中被创建,在其他地方被引用 1.1 方法的定义 一般 ...

  5. mysql语句 java变量_Java操作Mysql的方法

    本文实例讲述了Java操作Mysql的方法.分享给大家供大家参考.具体分析如下: 不同于C#操纵数据库的千篇一律,如果是要Java把操纵数据库的语句分为两种: 一种是有结果的select语句,还有一种 ...

  6. java ready()_Java.io.BufferedReader.ready()方法实例

    全屏 java.io.BufferedReader.ready()方法通知流是否已准备好被读取.一个缓冲字符流是只准备当缓冲区不为空,或者底层流已准备就绪. 声明 以下是java.io.Buffere ...

  7. java nextbyte()_java.util.Scanner.hasNextByte()方法实例

    全屏 java.util.Scanner.hasNextByte()如果在此scanner输入信息中的下一个标记可以使用nextByte()方法被解释为一个字节值的默认基数,方法返回true.scan ...

  8. rotate java 参数_java rotateLeft()和rotateRight()方法

    这两个方法实现的思想是循环左移和循环右移.首先来理解这两个概念 循环移位就是把数值变成二进制,然后循环移动的过程:换句话说,循环移位就是将移出的低位放到该数的高位(循环右移)或把移出的高位放到该数的低 ...

  9. future java 超时_Java使用Future设置方法超时

    1.Future 它提供了方法来检查是否计算已经完成,还是正在计算而处于等待状态,并且也提供了获取计算结果 方法.当计算完成后,只能通过get方法来获取执行结果,必要的话该方法会阻塞.通过cancel ...

  10. java 装配_java – 无法自动装配方法

    我收到了这个错误 org.springframework.beans.factory.BeanCreationException: Could not autowire method: 这是我的spr ...

最新文章

  1. sql查询父节点所有子节点id_5招搞定SQL棘手问题,同事看到直呼“内行”
  2. 软件设计师备考知识05--设计模式
  3. Android开发--图形图像与动画(五)--详解LayoutAnimationController
  4. UNIX环境编程学习笔记(6)——文件I/O之判断文件类型
  5. 【LeetCode】3月16日打卡-Day1
  6. 阿里电商架构演变之路(二)
  7. 对比了上百个python程序员的开发习惯,这10个方法最节省时间!
  8. mysql 下 计算 两点 经纬度 之间的距离
  9. 2020-09-10
  10. 计算机中丢失vulkan-1.dll,vulkan-1.dll
  11. 用pod安装swiftyJson的一个实例
  12. CFAR检测MATLAB仿真
  13. MLAPP————第十四章 核方法
  14. 密钥文件snk 、AssemblyInfo.cs
  15. vue中echarts实现甘特图
  16. 帝国cms如何给网站添加百度统计代码,百度统计安装教程步骤分享
  17. Java实现邮箱激活验证
  18. mysql core dumped_关于Segmentation fault (core dumped)几个简单问题的整理
  19. Java(SpringCloud) 使用Thymeleaf渲染模板,通过Mailgun发送邮件
  20. 计算机毕业设计Node.js+Vue交通违章举报平台(程序+源码+LW+部署)

热门文章

  1. 华为是怎样研发的(1)——概述
  2. 金税盘3.1、百旺V5、UKEY数据库
  3. 【小技巧】如何将PPT的图保持高分辨率导入到Word中
  4. python抽奖小程序_python实现简单的抽奖小程序,抽奖的内容从文件里面读取
  5. 微信文章编辑的html在哪里,微信公众号的文章编辑界面在哪里?怎么编辑排版? | 微信公众号指南...
  6. ZTE MF971V LTE Cat6 MiFi Review
  7. 计算机房电脑装软件,机房轻松批量安装软件
  8. Springboot集成Swagger接口测试工具
  9. 常用服务器管理口IP及账号密码(持续更新)
  10. 视频转图片,图片转视频 OpenCV-python实现