1. 计算机系统 使用高速缓存来作为内存与处理器之间的缓冲,将运算需要用到的数据复制到缓存中,让计算能快速进行;当运算结束后再从缓存同步回内存之中,这样处理器就无需等待缓慢的内存读写了。 缓存一致性:多处理器系统中,因为共享同一主内存,当多个处理器的运算任务都设计到同一块内存区域时,将可能导致各自的缓存数据不一致的情况,则同步回主内存时需要遵循一些协议。 乱序执行优化:为了使得处理器内部的运算单位能尽量被充分利用。 4 X! {$ i6 i+ s4 N( b2 R
2. java内存模型 目标是定义程序中各个变量的访问规则。(包括实例字段、静态字段和构成数组的元素,不包括局部变量和方法参数) - 所有的变量都存储在主内存中(虚拟机内存的一部分)。
- 每条线程都由自己的工作内存,线程的工作内存中保存了该线程使用到的变量的主内存副本拷贝,线程对变量的所有操作都必须在工作内存中进行,而不能直接读写主内存中的变量。
- 线程之间无法直接访问对方的工作内存中的变量,线程间变量的传递均需要通过主内存来完成。
a/ ]& Z8 W: R4 y' X1 B* j" c+ F4 Y
7 d3 L, h* s4 C! q& i内存间交互操作: Lock(锁定):作用于主内存中的变量,把一个变量标识为一条线程独占的状态。 Read(读取):作用于主内存中的变量,把一个变量的值从主内存传输到线程的工作内存中。 Load(加载):作用于工作内存中的变量,把read操作从主内存中得到的变量的值放入工作内存的变量副本中。 Use(使用):作用于工作内存中的变量,把工作内存中一个变量的值传递给执行引擎。 Assign(赋值):作用于工作内存中的变量,把一个从执行引擎接收到的值赋值给工作内存中的变量。 Store(存储):作用于工作内存中的变量,把工作内存中的一个变量的值传送到主内存中。 Write(写入):作用于主内存中的变量,把store操作从工作内存中得到的变量的值放入主内存的变量中。 Unlock(解锁):作用于主内存中的变量,把一个处于锁定状态的变量释放出来,之后可被其它线程锁定。 8 y( x5 Q8 }* Q+ {$ S5 l
规则: - 不允许read和load、store和write操作之一单独出现。
- 不允许一个线程丢弃最近的assign操作,变量在工作内存中改变了之后必须把该变化同步回主内存中。
- 不允许一个线程没有发生过任何assign操作把数据从线程的工作内存同步回主内存中。
- 一个新的变量只能在主内存中诞生。
- 一个变量在同一时刻只允许一条线程对其进行lock操作,但可以被同一条线程重复执行多次。
- 如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行read、load操作。
- 如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作。
- 8.对一个变量执行unlock操作前,必须先把该变量同步回主内存中。) ^: M8 ^6 M7 K. ?
8 W& X) \2 |; H0 j- g4 g5 j ?' A; j
3. volatile型变量 - 保证此变量对所有线程的可见性。每条线程使用此类型变量前都需要先刷新,执行引擎看不到不一致的情况。) T3 v" u# x( o- ?
运算结果并不依赖变量的当前值、或者确保只有单一的线程修改变量的值。 变量不需要与其他的状态变量共同参与不变约束。 - 禁止指令重排序优化。普通的变量仅保证在方法执行过程中所有依赖赋值结果的地方都能获取到正确的结果。而不能保证赋值操作的顺序与程序代码中的顺序一致。
- load必须与use同时出现;assign和store必须同时出现。4 y( |! x& ^/ {- i
, C; W3 F% J4 Q! v
4. 原子性、可见性与有序性 原子性:基本数据类型的访问读写是具备原子性的,synchronized块之间的操作也具备原子性。 可见性:指当一个线程修改了共享变量的值,其他线程能够立即得知这个修改。synchronized(规则8)和final可以保证可见性。Final修饰的字段在构造器中一旦被初始化完成,并且构造器没有把this的引用传递出去,那么在其他线程中就能看见final字段的值。 有序性:volatile本身包含了禁止指令重排序的语义,而synchronized则是由规则5获得的,这个规则决定了持有同一个所的两个同步块只能串行地进入。
! ~# x& ^; I6 a/ u( q5. 先行发生原则 Java内存模型中定义的两项操作之间的偏序关系,如果操作A先行发生于操作B,其实就是说在发生操作B之前,操作A产生的影响能被操作B观察到。 程序次序规则:在一个线程内,按照代码控制流顺序,在前面的操作先行发生于后面的操作。 管程锁定规则:一个unlock操作先行发生于后面对同一个锁的lock操作。 Volatile变量规则:对一个volatile变量的写操作先行发生于后面对这个变量的读操作。 线程启动规则:Thread对象的start()方法先行发生于此线程的每个操作。 线程终止规则:线程中的所有操作都先行发生于对此线程的终止检测。 线程中断规则:对线程的interrupt()方法的调用先行发生于被中断线程的代码检测中断事件的发生。 对象终结过则:一个对象的初始化完成先行发生于它的finalize()方法的开始。 传递性:如果操作A先行发生于操作B,操作B现象发生于操作C,那么就可以得出操作A先行发生于操作C的结论。 : d, s0 Y9 w4 s
时间上的先后顺序与先行发生原则之间基本上没有太大的关系。 ! Q- O2 o' z4 D' v; G% Z9 A4 V+ H
6. 线程实现 使用内核线程实现: 内核线程Kernel Thread:直接由操作系统内核支持的线程,这种线程由内核类完成线程切换,内核通过操纵调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。 轻量级进程Light Weight Process:每个轻量级进程都由一个内核线程支持。 局限性:各种进程操作都需要进行系统调用(系统调用代价相对较高,需要在用户态和内核态中来回切换);轻量级进程要消耗一定的内核资源,一次一个系统支持轻量级进程的数量是有限的。
$ z, f+ Z: s: r5 O3 L- d4 h使用用户线程实现: 用户线程:完全建立在用户空间的线程库上,系统内核不能直接感知到线程存在的实现。用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。所有的线程操作都需要用户程序自己处理。 混合实现: 将内核线程和用户线程一起使用的方式。操作系统提供支持的轻量级进程则作为用户线程和内核线程之间的桥梁。 ! A$ j1 E: n5 W5 |
Sun JDK,它的Windows版和Linux版都是使用一对一的线程模型来实现的,一条Java线程映射到一条轻量级进程之中。
0 G- n, @" Q2 z" o% r% Q( b7. 线程调度 线程调度是指系统为线程分配处理器使用权的过程:协同式、抢占式。 协同式:线程的执行时间由线程本身控制,线程把自己的工作执行完了之后,要主动通知系统切换到另一个线程上。坏处:线程执行时间不可控制。 抢占式:每个线程将由系统来分配执行时间,线程的切换不由线程本身来决定。Java使用该种调用方式。 线程优先级:在一些平台上(操作系统线程优先级比Java线程优先级少)不同的优先级实际会变得相同;优先级可能会被系统自行改变。 8 G( e; N0 T) C8 ^" [8 f# f. \1 e
8. 线程状态 线程状态: 新建NEW: 运行RUNNABLE: 无限期等待WAITING:等得其他线程显式地唤醒。 没有设置Timeout参数的Object.wait();没有设置Timeout参数的Thread.wait()。 限期等待TIMED_WAITING:在一定时间之后会由系统自动唤醒。 设置Timeout参数的Object.wait();设置Timeout参数的Thread.wait();Thread.sleep()方法。 阻塞BLOCKED:等待获取一个排它锁,等待进入一个同步区域。 结束TERMINATED:
7 b, U$ u; q* B" I8 m2 R0 U9. 线程安全 线程安全:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交换执行,也不需要进行额外的同步,或者调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象就是线程安全的。 0 Y3 L3 S* E0 ]" K1 j, S" p$ |+ C
不可变:只要一个不可变的对象被正确地构建出来。使用final关键字修饰的基本数据类型;如果共享数据是一个对象,那就需要保证对象的行为不会对其状态产生任何影响(String类的对象)。方法:把对象中带有状态的变量都申明为final,如Integer类。有:枚举类型、Number的部分子类(AtomicInteger和AtomicLong除外)。 绝对线程安全: 相对线程安全:对这个对象单独的操作是线程安全的。一般意义上的线程安全。 线程兼容:需要通过调用端正确地使用同步手段来保证对象在并发环境中安全地使用。 线程对立:不管调用端是否采取了同步措施,都无法在多线程环境中并发使用的代码。有:System.setIn()、System.setOut()、System.runFinalizersOnExit()
& J5 S/ J' p& {" a& o6 z10. 线程安全的实现方法 - 1.互斥同步:同步是指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一条线程使用。互斥方式:临界区、互斥量和信号量。/ D& L) C/ o! G( I$ ]
Synchronized关键字:编译后会在同步块前后分别形成monitorenter和monitorexit这两个字节码指令。这两个指令都需要一个引用类型的参数来指明要锁定和解锁的对象。如果没有明确指定对象参数,那就根据synchronized修饰的是实例方法还是类方法,去取对应的对象实例或Class对象来作为锁对象。 在执行monitorenter指令时,首先尝试获取对象的锁,如果没有被锁定或者当前线程已经拥有了该对象的锁,则将锁计数器加1,相应的执行moniterexit时,将锁计数器减1,当计数器为0时,锁就被释放了。如果获取对象锁失败,则当前线程就要阻塞等待。 0 Y: ?) C9 o/ T3 K& l' h
ReentrantLock相对synchronized的高级功能: 等待可中断:当持有锁的线程长期不释放锁时,正在等待的线程可以选择放弃等待,改为处理其他事情。 公平锁:多个线程在等待同一个锁时,必须按照申请锁的事件顺序来一次获取锁;而非公平锁在被释放时,任何一个等待锁的线程都有机会获得锁。Synchronized中的锁是非公平锁,ReentrantLock默认也是非公平锁。 锁绑定多个条件:一个ReentrantLock对象可以同时绑定多个Condition对象。 . T, L3 r# ]% d! w. R5 o
- 2.非阻塞同步:
3 X, D4 m: r# u. ?- g+ z2 u
基于冲突检测的乐观并发策略:先进行操作,如果没有其他线程争用共享数据,那操作就成功了;如果共享数据有争用,产生了冲突,那就再进行其他的补偿措施(一般是不断的尝试,直到成功为止)。 AtomicInteger等原子类中提供了方法实现了CAS指令。
' u' l1 t( ^) V( V& N% s, {# y( L可重入代码:可以在代码执行的任何时刻中断它,转而去执行另一段代码,而在控制权返回后,原来的程序不会出现任何错误。特征:不依赖存储在堆上的数据和公用的系统资源、用到的状态量都由参数传入,不调用非可重入的方法等。如果一个方法,它的返回结果是可以预测的,只要出入了相同的数据,就能返回相同的结果,那它就满足可重入性的要求。 线程本地存储:如果一段代码中所需要的数据必须与其它代码共享,那就看看这些共享数据的代码是否能保证在同一个线程中执行。 A. ThreadLocal类 ThreadLocal:线程级别的局部变量,为每个使用该变量的线程提供一个独立的变量副本,每个线程修改副本时不影响其他线程对象的副本。ThreadLocal实例通常作为静态私有字段出现在一个类中。
* c" p0 p4 c' `- F11. 锁优化 - 1.自旋锁
+ [7 l. ]0 U7 f( C1 Z# F/ `9 X5 R
为了让线程等待,让线程执行一个忙循环(自旋)。需要物理机器有一个以上的处理器。自旋等待虽然避免了线程切换的开销,带它是要占用处理器时间的,所以如果锁被占用的时间很短,自旋等待的效果就会非常好,反之自旋的线程只会白白消耗处理器资源。自旋次数的默认值是10次,可以使用参数-XX reBlockSpin来更改。 自适应自旋锁:自旋的时间不再固定,而是由前一次在同一个锁上的自旋时间及锁的拥有者的状态来决定。 % H7 H1 o L" x2 S. t9 t' B/ h
- 2.锁清除6 c, r+ e6 b# K, T) r
指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行清除(逃逸分析技术:在堆上的所有数据都不会逃逸出去被其它线程访问到,可以把它们当成栈上数据对待)。
& u$ d- Q2 a- G; M# a- 3.锁粗化8 B8 V2 F$ I; ~: t" T! J
如果虚拟机探测到有一串零碎的操作都对同一个对象加锁,将会把加锁同步的范围扩展到整个操作序列的外部。
/ c" g2 ?# }6 w$ G+ D
4 K; E- y2 a. l, a" c6 \HotSpot虚拟机的对象的内存布局:对象头(Object Header)分为两部分信息吗,第一部分(Mark Word)用于存储对象自身的运行时数据,另一个部分用于存储指向方法区对象数据类型的指针,如果是数组的话,还会由一个额外的部分用于存储数组的长度。 32位HotSpot虚拟机中对象未被锁定的状态下,Mark Word的32个Bits空间中25位用于存储对象哈希码,4位存储对象分代年龄,2位存储锁标志位,1位固定为0。 ! q" L+ { n* I7 V. l& }: `
|