Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致的更新,线程应该确保通过排他锁单独获得这个变量。

volatile借助Java内存模型保证所有线程能够看到最新的值。(内存可见性)

实现原理:

将带有volatile变量操作的Java代码转换成汇编代码后,可以看到多了个lock前缀指令(X86平台CPU指令)。这个lock指令是关键,在多核处理器下实现两个重要操作:

1.将当前处理器缓存行的数据写回到系统内存。

2.这个写回内存的操作会使其他处理器里缓存该内存地址的数据失效

如果了解计算机组成原理,可以知道CPU为了提高处理速度,不和内存直接进行交互,而是使用Cache(高速缓存,通过缓存数据交互速度和内存不是一个数量级,而同时Cache的存储容量也很小)。

从内存将数据读到缓存后,CPU进行一系列数据操作,而操作完成时间是不可知的。而JVM对带有volatile变量进行写操作时,会发送Lock前缀指令,将数据从缓存行写入到内存。写入内存还不够,因为其他线程的缓存行中数据还是旧的,Lock指令可以让其他CPU通过监听在总线上的数据,检查自己的缓存数据是否过期,如果缓存行的地址和总线上的地址相同,则将缓存行失效,下次该线程对这个数据操作时,会重新从内存中读取,更新到缓存行。

2.Synchronized

Synchronized也是经常用到的,它给人的印象一般是”重量级锁”。在JDK1.6后,对Synchronized进行了一系列优化,引入了偏向锁和轻量级锁,对锁的存储结构和升级过程。有效减少获得锁和释放锁带来的性能消耗。

Synchronized同步基础:

1.普通同步方法,锁是当前实例对象。 public synchronized void test(){…}

2.静态同步方法,锁是当前类的Class对象。public static synchronized void test(…){}

3.对于同步方法块,锁是Synchronized括号中里配置的对象。synchronized(instance){…}

用javap反编译class文件,可以看到Synchronized用的是monitorenter和monitorexit实现加锁。一个monitorenter必须要有monitorexit与之对应,所以同步方法会在异常处和方法返回处加入monitorexit指令。

3: monitorenter //注意此处,进入同步方法 4: aload_0 5: dup 6: getfield #2 // Field i:I 9: iconst_110: iadd11: putfield #2 // Field i:I14: aload_115: monitorexit //注意此处,退出同步方法

3.Java对象头

Synchronized用到的锁存在Java对象头里,若对象非数组类型,用32bit存储(2个字宽,32虚拟机一个字宽为4字节,一个字节8bit)

MarkWord存储和锁相关的信息:

锁有四个等级: 无锁->偏向锁->轻量级锁->重量级锁。如果存在竞争,就会不断升级,但不会降级。

给大家推荐一个java内部学习群:725633148 阿里Java高级大牛直播讲解知识点,分享知识,多年工作经验的梳理和总结 带着大家全面 科学地建立自己的技术体系和技术认知!

1.偏向锁

多数情况下,锁不会存在竞争,而是同一个线程多次获得。当某个线程访问同步块代码时,会将锁对象和栈帧中的锁记里存储锁偏向的线程ID,以后线程在进入和退出同步块时不需要进行CAS操作来加锁和解锁,只需简单比对一下对象头中的MarkWord里的线程ID,如果一致则表示线程获得锁。若不一致,再继续测试偏向锁的标识是否为1:如果没有设置(无锁状态),用CAS(Compare and Swap)竞争锁;如果设置了,尝试使用CAS将对象头的偏向锁指向当前线程。

当有另一个线程尝试竞争锁时,持有偏向锁的线程才会释放锁。需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,然后检查持有偏向锁的线程是否活着,如果线程不处于活动状态,则将对象头设置成无锁状态,如果线程仍然活着,拥有偏向锁的栈会被执行,遍历偏向对象的锁记录,栈中的锁记录和对象头的Mark Word,要么重新偏向于其他线程,要么恢复到无锁或者标记对象不适合作为偏向锁,最后唤醒暂停的线程。

Java 6,7默认开启偏向锁,可以通过JVM的参数-XX:-UsebiasedLocking=false关闭

2.轻量级锁

(1)加锁

锁记录存储在栈桢,会将对象头的MarkWord复制到锁记录。线程在执行同步块时,会尝试用CAS将对象头的MarkWord替换为指向锁记录的指针,若成功,获得锁;失败表示其他线程竞争锁,当前线程尝试使用自旋获取锁。

(2)解锁

类似于加锁反向操作,会将锁记录复制会对象头的MarkWord。若成功,表示操作过程中没有竞争发生;若失败,存在竞争,锁会膨胀成重量级锁。

如下图:

当膨胀到重量级锁时,不会再通过自选获得锁(自旋时线程处于活动状态,会消耗CPU),而是将线程阻塞,获得锁的线程执行完后会释放重量级锁,此时唤醒因为锁阻塞的线程,进行新一轮的竞争。

3.其他锁概念

自旋锁:

自旋锁是采用让当前线程不停地的在循环体内执行实现的,当循环的条件被其他线程改变时 才能进入临界区。

public class SpinLock { private AtomicReference sign =new AtomicReference<>(); public void lock(){

Thread current = Thread.currentThread(); while(!sign .compareAndSet(null, current)){

}

} public void unlock (){

Thread current = Thread.currentThread();

sign .compareAndSet(current, null);

}

}

使用了CAS原子操作,lock函数将owner设置为当前线程,并且预测原来的值为空。unlock函数将owner设置为null,并且预测值为当前线程。

当有第二个线程调用lock操作时由于owner值不为空,导致循环一直被执行,直至第一个线程调用unlock函数将owner设置为null,第二个线程才能进入临界区。

由于自旋锁只是将当前线程不停地执行循环体,不进行线程状态的改变,所以响应速度更快。但当线程数不停增加时,性能下降明显,因为每个线程都需要执行,占用CPU时间。如果线程竞争不激烈,并且保持锁的时间段。适合使用自旋锁。

给大家推荐一个java内部学习群:725633148 阿里Java高级大牛直播讲解知识点,分享知识,多年工作经验的梳理和总结 带着大家全面 科学地建立自己的技术体系和技术认知!

锁的优缺点对比:

优点缺点适用场景偏向锁加锁和解锁不需要额外的消耗,和执行非同步方法比仅存在纳秒级的差距如果线程间存在锁竞争,会带来额外的锁撤销的消耗适用于只有一个线程访问同步块场景轻量级锁竞争的线程不会阻塞,提高了程序的响应速度如果始终得不到锁竞争的线程使用自旋会消耗CPU追求响应时间,锁占用时间很短重量级锁线程竞争不使用自旋,不会消耗CPU线程阻塞,响应时间缓慢追求吞吐量,锁占用时间较长

java语言中 负责并发编程的机制是_Java并发编程艺术-并发机制的底层原理实现...相关推荐

  1. 在java语言中 ()方法是不可以继承的_Java 语言中,构造方法是不可以继承的。( )_学小易找答案...

    [简答题]需求分析完整话术 [简答题] [单选题]适用于场地面积有限,集装箱吞吐量较大的水陆联运码头的起重机系统是( )系统 [单选题]利用叉车或半挂车.汽车承载货物,连同车辆一起开上开下船,到达目的 ...

  2. Java语言中提供了三个日期类_Java语言学习(5)-Java中基础封装类(日期、时间类)...

    日期和时间封装类 1. Data类 Java日期和时间采用Data类.Data类在java.util包中. Data类构造函数: 1)       Data()   采用当前时间初始化对象: 2)   ...

  3. Java语言中的生僻知识

    最近有一首名叫<生僻字>的流行歌曲火遍大江南北,创作者给佶屈聱牙的生僻字,配上了优美明快的旋律,竟然让歌曲变得琅琅上口.悦耳动听起来,平时不太常见的拒人于千里之外的这些汉字也不再那么陌生, ...

  4. JAVA语言中数字、字符和字符串处理

    前言: JAVA语言中针对数字.数学运算.字符.字符串有专门的类,分别是Number.Math.Character和String. 1.数字: JAVA对数字处理的相关方法被封装在java.lang包 ...

  5. Java 语言中的函数编程

    Java 语言中的函数编程 利用闭包和高阶函数编写模块化的 Java 代码 如果您从事大型企业项目开发,您就会熟悉编写模块化代码的好处.良构的.模块化的代码更容易编写.调试.理解和重用.Java 开发 ...

  6. [转载]Java 语言中的函数编程

    Java 语言中的函数编程 如果您从事大型企业项目开发,您就会熟悉编写模块化代码的好处.良构的.模块化的代码更容易编写.调试.理解和重用.Java 开发人员的问题是,函数编程范型长期以来只是通过像 H ...

  7. Java 语言中十大“坑爹”功能!

    点击上方蓝色"程序猿DD",选择"设为星标" 回复"资源"获取独家整理的学习资料! 来源:https://www.sohu.com/a/35 ...

  8. JAVA语言异常,Java语言中的异常

    1.异常分类 从产生源头来看,Java语言中的异常可以分为两类: JVM抛出的异常.比如:访问null引用会引发NullPointerException:0作为除数,如9/0,JVM会抛出Arithm ...

  9. Java快速入门学习笔记7 | Java语言中的类与对象

    有人相爱,有人夜里开车看海,有人却连LeetCode第一题都解不出来!虽然之前系统地学习过java课程,但是到现在一年多没有碰过Java的代码,遇到LeetCode不知是喜是悲,思来想去,然后清空自己 ...

最新文章

  1. java变量存储位置_java 中变量存储位置的区别
  2. vue组件化学习第三天
  3. 常用抓包工具(可编程抓包工具)
  4. python脚本迁移数据库_Python迁移MySQL数据到MongoDB脚本
  5. java中文字符怎么保证出现正确_JAVA中文字符编码问题详解
  6. JS中用构造函数创建对象
  7. 思维导图下载 注册安全_【思维导图】初中各科思维导图,涵盖3年各科所有知识点,可下载打印!...
  8. linux怎样禁止他人远程,linux禁止用户远程登录的方法
  9. Java中获取当前类名和方法名
  10. 2、宽带Doherty放大器ADS仿真(带版图)
  11. 大智慧c语言dll,调用大智慧dll,简单支持大智慧公式dll接口
  12. python发送qq邮件失败_python发送QQ邮件
  13. ADIDAS的完整形式是什么?
  14. ShareREC for iOS录屏原理解析
  15. YOLOv5实现目标坐标打印并输出CSV文件
  16. weblogic 启动常见错误解决
  17. 【每天听见吴晓波-2016-07-04】上海房价未来五年还会翻番
  18. 设计分享 | STM32F103RCT6定时器定时中断原理
  19. 微软和谷歌又要“打”起来了!网友:太好了
  20. TextView控件的基本使用(各种属性的基本使用方法)

热门文章

  1. Bailian2688 求字母的个数【输入流】
  2. Head First 设计模式 —— 装饰器模式与门面模式
  3. 正则表达式 —— 一种模式描述语言(贪婪匹配与惰性匹配)
  4. 斐波那契数列的数学分析
  5. latex 基本用法(三)
  6. theano 编程细节
  7. css怎么实现加载的圆圈_图像高清方案——响应式图像让图像加载又快又省
  8. 遥感分类误差矩阵_遥感卫星影像之分类精度评价
  9. python3 asyncio 不阻塞_Python3 asyncio异步框架,让我崩溃的点
  10. 前端ui框架_推荐几个移动端前端UI框架