synchronized用法示例(synchronized是怎么实现的)

现在在出以Java多线程为主题的长系列教程,包含源码解读的入门至进阶,篇幅都会比较大,喜欢的朋友,给予关注就好啦~这篇纯理论的文章有点意思

相信对于synchronized在应用方面很多学生都不会感到陌生,前面已经为我们介绍了其应用方法了。但是它到底有哪些用法呢?这些用法都有些什么特点呢?我们又应该如何在面试中正确地运用它们呢?,其实每个人都会遇到这些问题。这篇文章主要带领你对其有一个深刻的认识,你还可以亲自尝试去总结,这个问题在面试时经常会被问到,简单地解答其基本用法,并不能让面试官们叹为观止~

直译为同步,因此又被称为同步锁,为了解决多线程并发所导致的各种问题,我们一般在某一个方法或某一块代码上添加Synchronized锁,这是目前使用最为普遍、最为简便的方式

Java中锁本质上是以对象为单位,因此也叫对象锁,类一般只包含1个class对象以及n个实例对象来分享class对象,我们有时给class对象添加锁,因此也叫class对象锁

在此,人们应该注意到,物体要求为非null物体,我们习惯上又称之为物体监视器(Object Monitor)

JDK 1.5以前,这是重量级的锁,我们一般用来确保线程的同步。现在我们要把JDK1.5升级为轻量级锁,这让我们可以将更多地精力放在如何更好地为系统服务上来,而不是仅仅关注于性能上的提升。1.5时也提供Lock接口,可以实现同步锁,大家只要显式获得锁、释放锁即可。

重点在哪里

1.5时,Synchronized其依赖操作系统底层Mutex Lock来执行,每释放一个锁与获得一个锁就造成用户态与内核态之间的转换,这就加大了系统性能开销,而大并发发生时,锁具竞争更加激烈,表现看起来很差,因此被叫做重量级锁具,因此人们倾向于选择Lock锁具。

但Synchronized这么简单好用,而且正式自带,如何能舍弃?如果你是个菜鸟级用户的话,就会发现一个奇怪的现象,当安装新版本后,系统的性能反而下降很多。原因很简单,就是由于系统中没有提供锁类服务造成的!1.6、为什么要进行锁优化:因为它可以把现有的自旋锁变成轻量级锁或者是偏向锁。

我们了解锁优化前,先研究其实现原理。

先来看看下面的同步块,由于是关键字的原因,没有看到源码的实现,只能反编译查看,用javap-v**…class的方式来实现

public static void main(String[] args) { synchronized(Demo.class) { System.out.println(“hello”); } }

public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=3, args_size=1 0: ldc 2: dup 3: astore_1 4: monitorenter 5: getstatic 8: ldc 10: invokevirtual 13: aload_1 14: monitorexit 15: goto 23 18: astore_2 19: aload_1 20: monitorexit 21: aload_2 22: athrow 23: return.

我们主要研究monitorenter与monitorexit,那两个人的意思比较高

在monitorenter中,若当前monitor输入个数为0,则线程输入monitor并将输入个数加1,则线程为monitor所有者(owner)。如果当前monitorentel中的所有线程都已被激活了,则在开始新任务之前,必须重新进入一个新线程;否则将继续原来的操作,直到所有线程全部激活为止。若线程已成为monitor所有者并再一次访问则将访问数再加1。即可重入。

在monitorexit中,完成monitor exits任务的线程一定要成为monitor所有者,命令完成时monitor进入数减去1,若减去1时进入数等于0,线程将退出monitor。当系统运行到某个阶段时,为了防止程序继续向前运行而产生死循环,需要对这个过程中的每个进程都进行检查,以判断其是否存在并发性错误。monitor的状态包括:未被访问、未被修改、未被删除、未被修改和未被改变所有权;指令有2次,第一次是同步的正常撤除释放锁,第二次是出现异步的撤除释放锁。

我们又看,修饰实例方法的性能:

class Demo { public synchronized void hello() { System.out.println(“hello”); } }

public synchronized void hello(); descriptor: ()V flags: ACC_PUBLIC, ACC_SYNCHRONIZED Code: stack=2, locals=1, args_size=1 0: getstatic 3: ldc 5: invokevirtual 8: return LineNumberTable: line 25: 0 line 26: 8 LocalVariableTable: Start Length Slot Name Signature 0 9 0 this Lcom/thread/base/Demo; }

我们主要研究ACC_SYNCHRONIZED的功能,其功能是一旦在该方法上实施,首先要对标志位的存在进行判断,若存在则首先试图取得monitor并取得成功才可以实施该方法,在该方法实施结束之后才可以解除monitor的限制。在方法执行过程中,我们发现了一个有趣现象:当程序运行完毕之后,很多线程都没有找到自己需要的monitor,这让人觉得很奇怪。所以,我们把这种机制叫做”死锁现象”.那么,如何避免这种情况发生呢?我认为可以通过以下两个方面进行:第一,使用多线程技术;第二,引入动态缓存。说到底就是竞争monitor对象,只不过同步方法以隐式方式进行。

JVM中synchronized的执行是以进、退monitor为基础,下层由一对MonitorEnter与MonitorExit指令执行

带着上述理解,接下来来了解一下锁优化

自旋锁,前面说到FutureTask源码,有个内部方法awaitDone(),对你有所介绍,都是在此基础上实现,今天又为你概述。

其目的在于避免阻塞与唤醒之间的转换,未得到锁时不会进入阻塞状态,并持续循环探测锁的解除。同时,这个算法还具有自适应性强的特点,能够根据当前系统状态自动调整自身的执行顺序,从而达到优化调度的效果。总之,本文提出的方法比其他现有方案更具优势。但同时也存在着缺点,一般情况下,单个线程所占的锁时间比较少,但如果占的时间比较长呢?我们可以通过增加一个线程的时间来达到这个目的,这就是我们常说的,当一个线程的时间为1.6小时时,就称为自适应自旋锁了。

在这种情况下,我们可以通过自适应自旋锁对每次的自旋进行一次检测,当检测到的自旋时间足够多时,则认为该自旋已经成功了,否则,则认为它还没有成功,因为它还没有成为真正意义上的拥有者;如果系统中存在多个不同类型的线程,且每个线程都可以实现正常工作时所需的功能,这时系统就需要对其进行控制,以保证每一个线程都能作到正确动作。如果这次自旋成功的话,那么下次非常可能还会成功,所以让自旋数量比较大,反过来如果几乎没有线程自旋成功的话,下次非常可能还失败的话自旋数量比较小。因此,如何有效地利用系统资源成为一个重要问题。

锁消除是本文提出的一种优化策略,它通过在JVM编译过程中加入一个新的机制来解决共享资源竞争问题。利用这种方法来提高JVM中共享内存使用效率。最后将这个算法应用于实际系统上,验证了其有效性和实用性。该优化策略能够消除不需要的锁定并除去获得锁定所需时间。

若连续进行一系列加锁解锁操作可能造成不必要的性能损失,因此引入了锁粗话这一思想。本文提出了一种新的设计思路:首先利用锁粗话来解决现有技术中存在的问题。然后通过修改原有算法来实现改进后的功能。最后通过实验证明了该方案的有效性。意即把若干个不断加锁和解锁的动作联系起来,展开成范围更大的锁具,这一点应被人们所认识

偏向锁由JDK 1.6推出,解决了哪些场景对于我们来说,多数用锁来解决多线程场景中的各种问题,但是有时通常一个线程就有这种情况,偏向锁就是单线程实现代码块时所采用的一种机制。

锁上的竞争其实就是Monitor物体之间的竞争,以及每一个物体具有物体头,物体头包括Mark Word与Klass pointer。当线程持有了该锁对象,标志位才开始发生变化;如果没有对象头,则不发生任何变化;若只有某个对象头,那么标志位也将改变。这样可以保证线程之间的同步。在有线程时,当锁对象被锁定到某一特定的标志位上时,它将以1的偏向模式运行,而当它被锁定到另一特定的ID上时,Mark Word则会执行相应的同步操作来提高系统的性能。

偏向锁是基于多线程实现的,本文提出了一个新的方法来实现偏向锁和轻量级锁之间的同步,即通过调用Mark Word来实现轻量级锁的同步。

在进行同步代码块前, JVM在线程的栈帧上建立锁记录(Lock Record).Mark Word复制为了防止在读取锁状态时发生错误,JVM需要对Mark Word2进行一次编译以识别出正确的锁状态。CAS操作是在Mark Word和锁记录之间进行的,它使用指针来控制Lock RECord的运行。如果成功,则进入获取锁状态,否则进入自旋获取锁状态;

自旋锁与重量级锁相类似,都是通过线程来完成任务。

如果悲观锁成功,则会把所有的线程都打开,从而避免了因为系统出现了问题而导致的系统无法正常工作的情况发生,甚至还可以防止由于系统出现故障而导致系统不能正常运行的情况发生,例如:当系统出现故障时,系统会自动进入一个新的状态,即”悲观锁”,这个状态下,系统不会再出现线程阻塞现象,而是直接进入一个新的阻塞队列。

synchronized通过锁升级策略适应各种场景,因此目前synchronized已经得到了很好的优化,这正是我们在工程上经常用到synchronized的原因。

这一节内容较多,你要很好地了解,尤其是锁具升级策略。锁具在日常生活中应用非常广泛,如:钥匙、防盗器、密码箱等等都需要用到锁具。下面我们就来了解一些锁具的使用方法和技巧吧!一起来看看吧!1。在这一小节中我们提及Lock锁,接下来,就带领你对Java Lock进行深入研究吧~

Java多线程专题中的线程和进程综述

Java多线程主题中的线程类与界面入门

Java多线程主题的进阶学习Thread,包括源码分析

Java多线程专题中的Callable,Future和FutureTask(包括源码分析)

面试官:是否知道线程组以及线程的优先级

面试官:谈谈线程生命周期流程

面试官:谈谈线程之间的沟通

面试者:谈谈Java共享内存模型

面试者:有没有人知道指令重排(happens-before)的含义

面试官:有没有知道volatile这个关键字,快说吧

我的博客(更好的阅读体验)

为初学者编写Java基础教程

一文带领大家迅速了解Java集合类的知识

用几分钟的时间迅速知道泛型和枚举

Java注解和反射的入门至进阶

《JavaIO教程》是一本由入门走向进阶的教程

java-thread-all

地址: https://github.com/qiuChengleiy/java-thread-all.git

springboot-all

地址: https://github.com/qiuChengleiy/springboot-all.git

SpringBoot系列教程合集

让我们来学习SpringCloud合集

原文链接:http://www.sfdkj.com/14040.html

© 版权声明
THE END
喜欢就支持一下吧
点赞9 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片