主要讲解Java的并发编程的基础知识,包括原子性、可见性、有序性,以及内存模型JMM。
Java系列说明
从这篇文章开始,我就要正式开始学习Java了,之所以说是从现在开始,是因为前两个月一直在纠结是否转技术栈(细心的同学可以发现,我之前写的文章,其实和Java并没有什么关系),现在已经想清楚了,既然确定要转Java技术栈,那就踏踏实实从头开始学吧。
目前的我,可以说是Java小白,刚转团队不久,也就接触了2个月的Java,代码没写几行,既然发现自己Java很菜,那就要列个学习计划,将这块知识好好补补。目前给自己定了一年的学习计划,希望能通过一年的学习,将Java的技能从初阶直接晋级到高阶水平,可能有同学会问“我学习Java都几年的,都还是中级水平,你花一年就可以晋级到高阶?”,我只想说,我想试试,毕竟工作这么长时间,也掌握了一定的学习方法,相信跟着自己的学习节奏走,应该不会离目标太远,今天立个Flag,希望一年后不会啪啪打脸【捂脸】~~
Java系列的内容主要包括并发编程、Spring、SpringBoost、SpringCloud、Tomcat、MyBatis、Dubbo和虚拟机,然后一些经典书籍的读书笔记等,当这些都掌握到一定深度后,我想我的Java技能应该也就差不多了。
最后想说的是,Java很多系列文章,很大一部分是内容整理,之所以要通过文章的形式再写一遍,是因为看过的内容,如果自己不整理一遍,或者不让程序跑跑,很容易遗忘,所以写文章其实不是目的,主要是重新整理和回顾学习内容的过程,一方面印象深刻,另一方面,也便于自己后续查阅。
今天废话有点多,我们就从并发编程开始吧!
并发编程基本概念
原子性
一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
原子性是拒绝多线程操作的,不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作。简而言之,在整个操作过程中不会被线程调度器中断的操作,都可认为是原子性。例如 a=1是原子性操作,但是a++和a +=1就不是原子性操作。Java中的原子性操作包括:
- 基本类型的读取和赋值操作,且赋值必须是值赋给变量,变量之间的相互赋值不是原子性操作;
- 所有引用reference的赋值操作;
- java.concurrent.Atomic.* 包中所有类的一切操作。
可见性
指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
在多线程环境下,一个线程对共享变量的操作对其他线程是不可见的。Java提供了volatile来保证可见性,当一个变量被volatile修饰后,表示着线程本地内存无效,当一个线程修改共享变量后他会立即被更新到主内存中,其他线程读取共享变量时,会直接从主内存中读取。当然,synchronize和Lock都可以保证可见性。synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码,并且在释放锁之前会将对变量的修改刷新到主存当中。因此可以保证可见性。
有序性
即程序执行的顺序按照代码的先后顺序执行。
Java内存模型中的有序性可以总结为:如果在本线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的。前半句是指“线程内表现为串行语义”,后半句是指“指令重排序”现象和“工作内存主主内存同步延迟”现象。
在Java内存模型中,为了效率是允许编译器和处理器对指令进行重排序,当然重排序不会影响单线程的运行结果,但是对多线程会有影响。Java提供volatile来保证一定的有序性。最著名的例子就是单例模式里面的DCL(双重检查锁)。另外,可以通过synchronized和Lock来保证有序性,synchronized和Lock保证每个时刻是有一个线程执行同步代码,相当于是让线程顺序执行同步代码,自然就保证了有序性。
为了让大家更好理解可见性和有序性,这个就不得不了解“内存模型”、“重排序”和“内存屏障”,因为这三个概念和他们关系非常密切。
内存模型
JMM决定一个线程对共享变量的写入何时对另一个线程可见,JMM定义了线程和主内存之间的抽象关系:共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存保存了被该线程使用到的主内存的副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。
对于普通的共享变量来讲,线程A将其修改为某个值发生在线程A的本地内存中,此时还未同步到主内存中去;而线程B已经缓存了该变量的旧值,所以就导致了共享变量值的不一致。解决这种共享变量在多线程模型中的不可见性问题,可以使用volatile、synchronized、final等,此时A、B的通信过程如下:
- 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去;
- 然后,线程B到主内存中去读取线程A之前已更新过的共享变量。
JMM通过控制主内存与每个线程的本地内存之间的交互,来为java程序员提供内存可见性保证,需要注意的是,JMM是个抽象的内存模型,所以所谓的本地内存,主内存都是抽象概念,并不一定就真实的对应cpu缓存和物理内存。
总结一句话,内存模型JMM控制多线程对共享变量的可见性!!!
重排序
重排序是指编译器和处理器为了优化程序性能而对指令序列进行排序的一种手段。
重排序需要遵守一定规则:
- 重排序操作不会对存在数据依赖关系的操作进行重排序。比如:a=1;b=a; 这个指令序列,由于第二个操作依赖于第一个操作,所以在编译时和处理器运行时这两个操作不会被重排序。
- 重排序是为了优化性能,但是不管怎么重排序,单线程下程序的执行结果不能被改变。 比如:a=1;b=2;c=a+b这三个操作,第一步(a=1)和第二步(b=
1 条评论
回复