再有人问你synchronized是什么,就把这篇文章发给他。

转载自   再有人问你synchronized是什么,就把这篇文章发给他。

在再有人问你Java内存模型是什么,就把这篇文章发给他中我们曾经介绍过,Java语言为了解决并发编程中存在的原子性、可见性和有序性问题,提供了一系列和并发处理相关的关键字,比如synchronized、volatile、final、concurren包等。

在《深入理解Java虚拟机》中,有这样一段话:

synchronized关键字在需要原子性、可见性和有序性这三种特性的时候都可以作为其中一种解决方案,看起来是“万能”的。的确,大部分并发控制操作都能使用synchronized来完成。

海明威在他的《午后之死》说过的:“冰山运动之雄伟壮观,是因为他只有八分之一在水面上。”

对于程序员来说,synchronized只是个关键字而已,用起来很简单。之所以我们可以在处理多线程问题时可以不用考虑太多,就是因为这个关键字帮我们屏蔽了很多细节。

那么,本文就围绕synchronized展开,主要介绍其用法、原理,以及如何提供原子性、可见性和有序性保障的等。

 

synchronized的用法

synchronized是Java提供的一个并发控制的关键字。主要有两种用法,分别是同步方法和同步代码块。

也就是说,synchronized既可以修饰方法也可以修饰代码块。代码如下:

/*** @author Hollis 18/08/04.*/
public class SynchronizedDemo {//同步方法public synchronized void doSth(){System.out.println("Hello World");}//同步代码块public void doSth1(){synchronized (SynchronizedDemo.class){System.out.println("Hello World");}}
}

synchronized修饰的代码块及方法,在同一时间,只能被单个线程访问。

 

synchronized的实现原理

synchronized,是Java中用于解决并发情况下数据同步访问的一个很重要的关键字。当我们想要保证一个共享资源在同一时间只会被一个线程访问到时,我们可以在代码中使用synchronized关键字对类或者对象加锁。

在深入理解多线程(一)——Synchronized的实现原理中我曾经介绍过其实现原理,为了保证知识的完整性,这里再简单介绍一下,详细的内容请去原文阅读。

我们对上面的代码进行反编译,可以得到如下代码:

public synchronized void doSth();descriptor: ()Vflags: ACC_PUBLIC, ACC_SYNCHRONIZEDCode:stack=2, locals=1, args_size=10: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;3: ldc           #3                  // String Hello World5: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V8: returnpublic void doSth1();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=3, args_size=10: ldc           #5                  // class com/hollis/SynchronizedTest2: dup3: astore_14: monitorenter5: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;8: ldc           #3                  // String Hello World10: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V13: aload_114: monitorexit15: goto          2318: astore_219: aload_120: monitorexit21: aload_222: athrow23: return

通过反编译后代码可以看出:

对于同步方法,JVM采用ACC_SYNCHRONIZED标记符来实现同步。 

对于同步代码块。JVM采用monitorentermonitorexit两个指令来实现同步。

在The Java® Virtual Machine Specification中有关于同步方法和同步代码块的实现原理的介绍,我翻译成中文如下:

方法级的同步是隐式的。同步方法的常量池中会有一个ACC_SYNCHRONIZED标志。当某个线程要访问某个方法的时候,会检查是否有ACC_SYNCHRONIZED,如果有设置,则需要先获得监视器锁,然后开始执行方法,方法执行之后再释放监视器锁。这时如果其他线程来请求执行方法,会因为无法获得监视器锁而被阻断住。值得注意的是,如果在方法执行过程中,发生了异常,并且方法内部并没有处理该异常,那么在异常被抛到方法外面之前监视器锁会被自动释放。

 

同步代码块使用monitorentermonitorexit两个指令实现。可以把执行monitorenter指令理解为加锁,执行monitorexit理解为释放锁。 每个对象维护着一个记录着被锁次数的计数器。未被锁定的对象的该计数器为0,当一个线程获得锁(执行monitorenter)后,该计数器自增变为 1 ,当同一个线程再次获得该对象的锁的时候,计数器再次自增。当同一个线程释放锁(执行monitorexit指令)的时候,计数器再自减。当计数器为0的时候。锁将被释放,其他线程便可以获得锁。

无论是ACC_SYNCHRONIZED还是monitorentermonitorexit都是基于Monitor实现的,在Java虚拟机(HotSpot)中,Monitor是基于C++实现的,由ObjectMonitor实现。

ObjectMonitor类中提供了几个方法,如enterexitwaitnotifynotifyAll等。sychronized加锁的时候,会调用objectMonitor的enter方法,解锁的时候会调用exit方法。(关于Monitor详见深入理解多线程(四)—— Moniter的实现原理)

 

synchronized与原子性

原子性是指一个操作是不可中断的,要全部执行完成,要不就都不执行。

我们在Java的并发编程中的多线程问题到底是怎么回事儿中分析过:

线程是CPU调度的基本单位。CPU有时间片的概念,会根据不同的调度算法进行线程调度。当一个线程获得时间片之后开始执行,在时间片耗尽之后,就会失去CPU使用权。所以在多线程场景下,由于时间片在线程间轮换,就会发生原子性问题。

在Java中,为了保证原子性,提供了两个高级的字节码指令monitorentermonitorexit

前面介绍过,这两个字节码指令,在Java中对应的关键字就是synchronized

通过monitorentermonitorexit指令,可以保证被synchronized修饰的代码在同一时间只能被一个线程访问,在锁未释放之前,无法被其他线程访问到。

因此,在Java中可以使用synchronized来保证方法和代码块内的操作是原子性的。

线程1在执行monitorenter指令的时候,会对Monitor进行加锁,加锁后其他线程无法获得锁,除非线程1主动解锁。即使在执行过程中,由于某种原因,比如CPU时间片用完,线程1放弃了CPU,但是,他并没有进行解锁。而由于synchronized的锁是可重入的,下一个时间片还是只能被他自己获取到,还是会继续执行代码。直到所有代码执行完。这就保证了原子性。

 

synchronized与可见性

可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

我们在再有人问你Java内存模型是什么,就把这篇文章发给他中分析过:

Java内存模型规定了所有的变量都存储在主内存中,每条线程还有自己的工作内存,线程的工作内存中保存了该线程中是用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存。不同的线程之间也无法直接访问对方工作内存中的变量,线程间变量的传递均需要自己的工作内存和主存之间进行数据同步进行。所以,就可能出现线程1改了某个变量的值,但是线程2不可见的情况。

前面我们介绍过,被synchronized修饰的代码,在开始执行时会加锁,执行完成后会进行解锁。

而为了保证可见性,有一条规则是这样的:对一个变量解锁之前,必须先把此变量同步回主存中。这样解锁后,后续线程就可以访问到被修改后的值。

所以,synchronized关键字锁住的对象,其值是具有可见性的。

 

synchronized与有序性

有序性即程序执行的顺序按照代码的先后顺序执行。

我们在再有人问你Java内存模型是什么,就把这篇文章发给他中分析过:

除了引入了时间片以外,由于处理器优化和指令重排等,CPU还可能对输入代码进行乱序执行,比如load->add->save 有可能被优化成load->save->add 。这就是可能存在有序性问题。

这里需要注意的是,synchronized是无法禁止指令重排和处理器优化的。也就是说,synchronized无法避免上述提到的问题。

那么,为什么还说synchronized也提供了有序性保证呢?

这就要再把有序性的概念扩展一下了。

Java程序中天然的有序性可以总结为一句话:如果在本线程内观察,所有操作都是天然有序的。如果在一个线程中观察另一个线程,所有操作都是无序的。

以上这句话也是《深入理解Java虚拟机》中的原句,但是怎么理解呢?周志明并没有详细的解释。这里我简单扩展一下,这其实和as-if-serial语义有关。

as-if-serial语义的意思指:不管怎么重排序(编译器和处理器为了提高并行度),单线程程序的执行结果都不能被改变。编译器和处理器无论如何优化,都必须遵守as-if-serial语义。

这里不对as-if-serial语义详细展开了,简单说就是,as-if-serial语义保证了单线程中,指令重排是有一定的限制的,而只要编译器和处理器都遵守了这个语义,那么就可以认为单线程程序是按照顺序执行的。当然,实际上还是有重排的,只不过我们无须关心这种重排的干扰。

所以呢,由于synchronized修饰的代码,同一时间只能被同一线程访问。那么也就是单线程执行的。所以,可以保证其有序性。

 

synchronized与锁优化

前面介绍了synchronized的用法、原理以及对并发编程的作用。是一个很好用的关键字。

synchronized其实是借助Monitor实现的,在加锁时会调用objectMonitor的enter方法,解锁的时候会调用exit方法。事实上,只有在JDK1.6之前,synchronized的实现才会直接调用ObjectMonitor的enterexit,这种锁被称之为重量级锁。

所以,在JDK1.6中出现对锁进行了很多的优化,进而出现轻量级锁,偏向锁,锁消除,适应性自旋锁,锁粗化(自旋锁在1.4就有,只不过默认的是关闭的,jdk1.6是默认开启的),这些操作都是为了在线程之间更高效的共享数据 ,解决竞争问题。

关于自旋锁、锁粗化和锁消除可以参考深入理解多线程(五)—— Java虚拟机的锁优化技术,关于轻量级锁和偏向锁,已经在排期规划中,我后面会有文章单独介绍,将独家发布在我的博客(http://www.hollischuang.com)和公众号(Hollis)中,敬请期待。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/324918.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基本数据类型、包装类、String三者之间的相互转换

package com.wdl.day13;import org.junit.Test;/** 包装类的使用:* 1.java提供了8种基本数据类型对应的包装类,使得基本数据类型的变量具有类的特征** 2.掌握的:基本数据类型、包装类、String三者之间的相互转换****/ public class WrapperTest {//Strin…

很简单很简单的DBHelper类

记录一个简单的DBHelper类吧&#xff0c;用的时候在上来拿&#xff01; /// <summary>/// 数据库连接工具类/// </summary>public class DBHelper{string constr "Data Source.;Initial CatalogschoolDB;Integrated SecurityTrue";private SqlConnecti…

洛谷P3371-【模板】单源最短路【SPFA】

题目 一个有向图&#xff0c;求一点到所有点的最短距离 输入 4 6 1(4个点&#xff0c;6条边&#xff0c;从1出发) 1 2 2(1点到2点有一条权值2的线) 2 3 2 2 4 1 1 3 5 3 4 3 1 4 4 输出 0 2 4 3 解题思路 这题数据非常的大 说明 时空限制&#xff1a;1000ms,128M …

DDD理论学习系列(1)-- 通用语言

1.引言 在开始之前&#xff0c;我想我们有必要先了解以下DDD的主要参与者。因为毕竟语言是人说的吗&#xff0c;就像我们面向对象编程一样&#xff0c;那通用语言面向的是&#xff1f;DDD的主要参与者&#xff1a;领域专家开发人员领域专家&#xff1a;精通业务的任何人。开发…

Java中枚举的线程安全性及序列化问题

转载自 Java中枚举的线程安全性及序列化问题 Java SE5提供了一种新的类型-Java的枚举类型&#xff0c;关键字enum可以将一组具名的值的有限集合创建为一种新的类型&#xff0c;而这些具名的值可以作为常规的程序组件使用&#xff0c;这是一种非常有用的功能。本文将深入分析枚…

Servlet 参数读取

1、配置参数读取的意义&#xff1a; 把参数提取到配置的信息中这样就大大的增加了整个代码的使用性方面后期的代码维护 需要知道&#xff0c; 在这里的参数读取仅仅是读到程序里面。 2、实现代码 package com.bjsxt.servlet;import javax.servlet.ServletException; import j…

定了!对于本周四(7.16日)抽奖活动取消简要说明,新抽奖活动暂定下周三(7.22日)...

大家好&#xff0c;我是雄雄&#xff0c;对于本周四&#xff08;7.16日&#xff09;抽奖活动取消简要说明&#xff0c;新抽奖活动暂定下周三&#xff08;7.22日&#xff09;&#xff0c;欢迎各位粉丝积极参与&#xff0c;奖品已经准备好了&#xff0c;你&#xff01;准备好了吗…

洛谷P1144-最短路计算【日常最短路,日常图论,SPFA】

题目 一个无向图&#xff0c;求点1到每个点的最短路的路径数量 输入 5 7(5个点,7条边) 1 2(表示1到2有边) 1 3 2 4 3 4 2 3 4 5 4 5 输出 &#xff08;答案mod100003&#xff09; 1 1 1 2 4 解题思路 注意这是无向图&#xff0c;然后请看数据范围 对于20%的数…

RabbitMQ系列教程之二:工作队列(Work Queues)

今天开始RabbitMQ教程的第二讲&#xff0c;废话不多说&#xff0c;直接进入话题。 (使用.NET 客户端 进行事例演示) 在第一个教程中&#xff0c;我们编写了一个从命名队列中发送和接收消息的程序。在本教程中&#xff0c;我们将创建一个工作队列&#xff0c;这个队列将用于在…

单例 (Singleton)设计模式

所谓类的单例设计模式&#xff0c;就是采取一定的方法保证在整个的软件系统中&#xff0c;对某个类只能存在一个对象实例&#xff0c;并且该类只提供一个取得其对象实例的方法。如果我们要让类在一个虚拟机中只能产生一个对象&#xff0c;我们首先必须将类的构造器的访问权限设…

面试中经常会问的智力题,来看看你会做几道

转载自 面试中经常会问的智力题&#xff0c;来看看你会做几道 下面是大部分题目来自滴滴出行2017秋招题。开始头脑风暴吧~~~ 问题 question one 有50家人家&#xff0c;每家一条狗。有一天警察通知&#xff0c;50条狗当中有病狗&#xff0c;行为和正常狗不一样。每人只能通…

你喜欢什么样的课堂?

最近看了一本书《让课堂充满幽默》&#xff0c;里面有些内容还是觉得挺有道理的。书中开头就说&#xff1a;“大量的课件、复杂的网络&#xff0c;使得老师似乎成了信息管理员和媒体播放机&#xff0c;学生则成为了被灌输的对象”&#xff0c;看后&#xff0c;内心深处反问自己…

Servlet 中文乱码处理

1、为什么使用中文乱码 我们在实现登录时候 &#xff0c;需要进行前台的数据。获得数据以后可能会出现中文乱码&#xff0c;那应该如何处理呢&#xff1f; 2、get方式和Post提交方式的区别 [1]get数据的传输是不安全的 &#xff0c;post数据传递更加安全 [2]get方式数据传递有大…

洛谷P1346-电车【日常图论,最短路,SPFA】

题目 一个有向图&#xff0c;每个点有个默认方向和若干个其他方向&#xff0c;走默认方向权值为0&#xff0c;其他方向权值为1&#xff0c;求最短路 输入 3 2 1(3个点&#xff0c;点2到点1) 2 2 3&#xff08;2个点&#xff0c;起点为1&#xff0c;2为默认点&#xff0c;3为…

main()方法

main()方法的使用说明&#xff1a; main()方法作为程序的入口main()方法也是一个普通的静态方法main()方法可以作为我们与控制台交互的方式。&#xff08;之前&#xff1a;使用Scanner&#xff09;

实现自己的.NET Core配置Provider之EF

《10分钟就能学会.NET Core配置》里详细介绍了.NET Core配置的用法&#xff0c;另外我还开源了自定义的配置Provider&#xff1a;EF配置Provider和Yaml配置Provider。本文先来聊聊EF配置Provider的实现&#xff0c;其中会涉及到EntityFramework Core的知识&#xff0c;不熟悉也…

今天的雪糕格外好吃!

赤日炎炎&#xff0c;室外的温度超过30℃。午休罢&#xff0c;教室里一片寂静&#xff0c;大家都有一个目的——等待老师的进来&#xff0c;继续上课。偶尔有几位同学貌似等待焦急&#xff0c;遂将目光瞥向窗外&#xff0c;若有所思。还有几位好动同学在自己的座位上左右摇动&a…

学习分布式不得不会的BASE理论

转载自 学习分布式不得不会的BASE理论 eBay的架构师Dan Pritchett源于对大规模分布式系统的实践总结&#xff0c;在ACM上发表文章提出BASE理论&#xff0c;BASE理论是对CAP理论的延伸&#xff0c;核心思想是即使无法做到强一致性&#xff08;Strong Consistency&#xff0c;…

Servelt 中文乱码

1、为什么使用中文乱码 我们在实现登录时候 &#xff0c;需要进行前台的数据。获得数据以后可能会出现中文乱码&#xff0c;那应该如何处理呢&#xff1f; 2、get方式和Post提交方式的区别 [1]get数据的传输是不安全的 &#xff0c;post数据传递更加安全 [2]get方式数据传递有大…

洛谷P2296-寻找道路【日常图论,最短路,SPFA】

题目 一个有向图&#xff0c;要求满足要求的最短路径&#xff0c;要求为&#xff1a; 路径上的所有点的出边所指向的点都直接或间接与终点连通。 输入1 3 2 (3个点,2条边) 1 2 (1和2之间可以连接) 2 1 1 3 (从1到3) 输出1 -1 输入2 6 6 1 2 1 3 2 6 2 5 4 5 3…