1.概述数据库
a.多任务处理的必要性:数组
b.硬件的效率与一致性缓存
为了更好的理解Java内存模型,先理解物理计算机中的并发问题,二者有很高的可比性。安全
为了平衡计算机的存储设备与处理器的运算速度之间几个数量级的差距,引入一层高速缓存(Cache)来做为内存与处理器之间的缓冲:网络
可是基于高速缓存的存储交互在多处理器系统中会带来缓存一致性(Cache Coherence)的问题。这是由于每一个处理器都有本身的高速缓存,而它们又共享同一主内存(Main Memory),当多个处理器的运算任务都涉及同一块主内存区域时,就可能致使各自的缓存数据不一致。解决办法就是须要各个处理器访问缓存时都遵循一些协议,在读写时要根据协议来进行操做。以下图。多线程
所以,这里所说的内存模型能够理解为:在特定的操做协议下,对特定的内存或高速缓存进行读写访问的过程抽象。并发
2.Java内存模型(Java Memory Model,JMM)oop
a.目的:屏蔽掉各类硬件和操做系统的内存访问差别,实现Java程序在各类平台下都能达到一致的内存访问效果。post
b.方法:经过定义程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出变量这样的底层细节。性能
注意:这里的变量与Java中说的变量不一样,而指的是实例字段、静态字段和构成数组对象的元素,但不包括局部变量与方法参数,由于后者是线程私有的,不会被共享,天然就不会存在竞争问题。
c.结构:模型结构如图,和上张图进行类比。
注意:这里的主内存、工做内存与要点提炼| 理解JVM以内存管理说的Java内存区域中的Java堆、栈、方法区等并非同一个层次的内存划分。
注意:
- 线程对变量的全部操做都必须在工做内存中进行,而不能直接读写主内存中的变量。
- 不一样的线程之间也没法直接访问对方工做内存中的变量,线程间变量值的传递必须经过主内存来完成。
lock
):把变量标识为一条线程独占的状态。unlock
):把处于锁定状态的变量释放出来。read
):把变量的值从主内存传输到线程的工做内存中,以便随后的load
动做使用。load
):把read
操做从主内存中获得的变量值放入工做内存的变量副本中。use
):把工做内存中一个变量的值传递给执行引擎。assign
):把从执行引擎接收到的值赋给工做内存的变量。store
):把工做内存中变量的值传送到主内存中,以便随后的write
操做使用。write
):把store
操做从工做内存中获得的变量的值放入主内存的变量中。结论:注意是顺序非连续
- 若是要把变量从主内存复制到工做内存,那就要顺序地执行
read
和load
。- 若是要把变量从工做内存同步回主内存,就要顺序地执行
store
和write
。
d.确保并发操做安全的原则:
①在Java内存模型中规定了执行上述8种基本操做时须要知足以下规则:
read
和load
、store
和write
操做之一单独出现,即不容许一个变量从主内存读取了但工做内存不接受,或者从工做内存发起回写了但主内存不接受的状况出现。assign
操做,即变量在工做内存中改变了以后必须把该变化同步回主内存。assign
操做就把数据从线程的工做内存同步回主内存中。load
或assign
)的变量,即对一个变量实施use
、store
操做以前必须先执行过了assign
和load
操做。lock
操做,但lock
操做能够被同一条线程重复执行屡次,屡次执行lock
后,只有执行相同次数的unlock
操做,变量才会被解锁。lock
操做,那将会清空工做内存中此变量的值,在执行引擎使用这个变量前,须要从新执行load
或assign
操做初始化变量的值。lock
操做锁定,那就不容许对它执行unlock
操做,也不容许去unlock
一个被其余线程锁定住的变量。unlock
操做以前,必须先把此变量同步回主内存中。可见这么多规则很是繁琐,实践也麻烦,下面再介绍一个等效判断原则--先行发生原则。
②先行发生原则:是Java内存模型中定义的两项操做之间的偏序关系。下面例举一些“自然的”先行发生关系,无须任何同步器协助就已经存在,能够在编码中直接使用。
start()
先行发生于此线程的每个动做。Thread.join()
结束、Thread.isAlive()
的返回值等手段检测到线程已经终止执行。interrupt()
的调用先行发生于被中断线程的代码检测到中断事件的发生。可经过Thread.interrupted()
检测到是否有中断发生。finalize()
的开始。e.Java内存模型保证并发过程的原子性、可见性和有序性的措施:
read
、load
、assign
、use
、store
和write
,所以可认为基本数据类型的访问读写是具有原子性的。monitorenter
和monitorexit
来隐式地使用lock
和unlock
这两个操做,反映到Java代码中就是同步代码块synchronized
关键字。volatile
能保证新值能当即同步到主内存,且每次使用前当即从主内存刷新;synchronized
对一个变量执行unlock操做以前能够先把此变量同步回主内存中;被final
修饰的字段在构造器中一旦初始化完成且构造器没有把this
的引用传递出去,就能够在其余线程中就能看见final字段的值。volatile
自己就包含了禁止指令重排序的语义;synchronized
保证一个变量在同一个时刻只容许一条线程对其进行lock操做,使得持有同一个锁的两个同步块只能串行地进入。3.Java与线程
a.线程实现的三种方式
①使用内核线程(Kernel-Level Thread,KLT)
②使用用户线程(User Thread,UT)
③使用用户线程加轻量级进程混合
那么Java线程的实现是选择哪种呢?答案是不肯定的。操做系统支持怎样的线程模型,在很大程度上决定了Java虚拟机的线程是怎样映射的。线程模型只对线程的并发规模和操做成本产生影响,而对Java程序的编码和运行过程来讲,这些差别都是透明的。
b.Java线程调度的两种方式
线程调度:指系统为线程分配处理器使用权的过程。
①协同式线程调度(Cooperative Threads-Scheduling)
②抢占式线程调度(Preemptive Threads-Scheduling)
可是线程优先级并非太靠谱,一方面由于Java的线程是经过映射到系统的原生线程上来实现的,因此线程调度最终仍是取决于操做系统,在一些平台上不一样的优先级实际会变得相同;另外一方面优先级可能会被系统自行改变。
c.线程的五种状态
在任意一个时间点,一个线程只能有且只有其中的一种状态:
Object.wait()
Thread.join()
LockSupport.park()
Thread.sleep()
Object.wai()
Thread.join()
LockSupport.parkNanos()
LockSupport.parkUntil()
注意区别:
- 阻塞状态:在等待获取到一个排他锁,在另一个线程放弃这个锁的时候发生;
- 等待状态:在等待一段时间或者唤醒动做的发生,在程序等待进入同步区域的时候发生。
下图是线程状态之间的转换: