JVM内存模型和性能优化
JVM内存模型优势html
- 内置基于内存的并发模型: 多线程机制
- 同步锁Synchronization
- 大量线程安全型库包支持
- 基于内存的并发机制,粒度灵活控制,灵活度高于数据库锁。
- 多核并行计算模型
- 基于线程的异步模型。
JVM性能的人为问题java
- 关键缘由是:没有正确处理好对象的生命周期。
- 须要从需求中找出存在天然边界的业务对象,将其对应落实到内存中,成为内存模型In-memory Domain Model。
- 有大小边界限制的内存是缓存,没有永远使用不完的内存,缓存=“有边界的”内存。
- 缓存是Domain Model对象缓存,不一样于传统意义上数据库缓存的定义。
- 分布式缓存能够提升巨量数据处理计算能力。
Java内存种类git
- Stack栈内存
存取速度快,数据可多线程间共享。
存在栈中的数据大小与生存期必须肯定
- Heap堆内存
大小动态变化,对象的生命周期没必要事先告诉编译器JVM。
两种内存使用github
- Stack栈内存
基本数据类型,Java 指令代码,常量
对象实例的引用 对象的方法代码
- Heap堆内存
对象实例的属性数据和数组。堆内存由Java虚拟机的自动垃圾回收器来管理。
对象如何保存在内存中?算法
- 对象的属性Attribute Property
属性值做为数据,保存在数据区heap 中,包括属性的类型Classtype和对象自己的类型数据库
- 方法method
方法自己是指令的操做码,保存在stack中。
方法内部变量做为指令的操做数也是在Stack中,
包括基本类型和其余对象的引用。数组
- 对象实例在heap 中分配好内存之后,须要在stack中保存一个4字节的heap内存地址,用来定位该对象实例在heap 中的位置,便于找到该对象实例。
静态属性和方法的特色缓存
- 静态属性和方法都是保存在Stack中,
- Stack内存是共享的,其余线程均可以访问静态属性实际是全局变量。
- 静态方法在Stack,就没法访问Heap中的数据。静态方法没法访问普通对象中数据。
- 静态属性意味着全局变量,生命周期和JVM一致。JVM属于技术边界,静态只能用于技术边界内工具性质使用,不能用做业务。
内存管理:垃圾回收机制安全
- 每一种垃圾收集的算法(引用计数、复制、标记-清除和标记-整理等)在特定条件下都有其优势和缺点。
- 当有不少对象成为垃圾时,复制能够作得很好,可是复制许多生命周期长的对象时它就变得很糟(要反复复制它们)。
- 标记-整理适合生命周期长对象能够作得很好(只复制一次),可是不适合短生命的对象。
- Sun JVM 1.2 及之后版本使用的技术称为 分代垃圾收集(generational garbage collection),它结合了这两种技术以结合两者的长处。
可选用的GC类型性能优化

JVM性能优化
- 内存微调优化
- 锁争夺微调:
多线程 不变性 单写原则 Actor Disrupotor
- CPU使用率微调
- I/O 微调
内存微调优化
- 内存分配:
新生代 Eden和survior 旧生代内存大小分配。
内存越大,吞吐量越大,可是须要内存整理的时间就越长,响应时间有延迟。
- 垃圾回收机制
垃圾回收启动整个应用都暂停,暂停时间形成响应时间有延迟。
内存微调目标
- 在延迟性(响应时间)和吞吐量上取得一个平衡。
- 内存大小影响吞吐量和延迟性。须要在内存大小和响应时间之间取得一个平衡。
- 垃圾回收机制是延迟的最大问题。目标尽可能不启动,少启动。

内存模型

新生代Eden内存分配
- 新生代(New Generation ):Eden + 1 Survivor。全部新建立的对象在Eden。
- 当Eden满了,启动Stop-The-World的GC,或为minor gc,采起数次复制Copy-Collection到Survivor。
- 通过几回收集,寿命不断延长的对象从Survivor 进入老生代,也称为进入保有Tenuring,相似普通缓存LRU算法。
survivor设计要旨
- 足够大到能容纳全部请求响应中涉及的对象数据。
- 每一个survivor空间也要足够大到可以容纳活跃的请求对象和保有对象。
- Survivor大小决定了保有Tenuring阀值,阀值若是能大到容纳全部常住对象,那么保有迁移过程就越快。
老生代Old
- 老生代的gc称为major gc,就是一般说的full gc。
- 采用标记-整理算法。因为老年区域比较大,并且一般对象生命周期都比较长,标记-整理须要必定时间。因此这部分的gc时间比较长。
- minor gc可能引起full gc。当eden+from space的空间大于老生代的剩余空间时,会引起full gc。这是悲观算法,要确保eden+from space的对象若是都存活,必须有足够的老生代空间存放这些对象。
- 这些都根据状况调整启动JVM的设置。
- 使用 Adaptive让JVM自动划分新生代和老生代。
Permanent Generation 永久代
- 该区域比较稳定,主要用于存放classloader信息,好比类信息和method信息。
- 缺省是 64M ,若是你的代码量很大,容易出现OutOfMemoryError: PermGen space 。
- 2G以上内存设置MaxPermSize为160M
- -XX:PermSize=128m -XX:MaxPermSize=160m
下降Full GC发生几率
- 为了下降Full GC发生几率,若是下降了老生代大小,那么 OutOfMemoryError 发生,Full GC几率反而会上升。
- 若是为了下降Full GC,增长老生代大小,执行时间可能会被延长。
- 必须寻找合适大小的老生代。
- 避免大的对象迁移到老生代。
- 减小迁移到老生代的对象数目
java.lang.OutOfMemoryError
- (1)在高负荷的状况下的却须要很大的内存,所以能够经过修改JVM参数来增长Java Heap Memory。
- (2)应用程序使用对象或者资源没有释放,致使内存消耗持续增长,关键采起OO封装边界方式,树立对象都有生命周期的基本习惯。
- (3)再一种也多是对于第三方开源项目中资源释放了解不够致使使用之后资源没有释放(例如JDBC的ResultSet等)。
JVM参数
- -Xms, -Xmx—定义JVM的heap大小最小和最大值。
- -XX:NewSize— 定义年轻态的最小大小,Eden越大越好,可是越大响应有延迟。
- -Xmx2G -Xms1G -XX:NewSIze=512M (OldGen at least 1G)
- -Xmx3G -Xms1G -XX:NewSize=512M (OldGen at least 2G)
- Xmx4G -Xms2G -XX:NewSize=1G (OldGen at least 2.5G)
- -Xmx6G -Xms3G -XX:NewSize=2G (OldGen at least 3.5G)
- -Xmx8G -Xms4G -XX:NewSize=3G (OldGen at least 4.5G)
参数调整示意
- JAVA_OPTS="$JAVA_OPTS -server -Xss1280K -Xms1664m -Xmx1664m -XX:MaxPermSize=128m -XX:SurvivorRatio=16 -XX:NewSize=1280m -XX:MaxNewSize=1280m -XX:+DisableExplicitGC -XX:GCTimeRatio=2 -XX:ParallelGCThreads=4 -XX:+UseParNewGC -XX:MaxGCPauseMillis=2000 -XX:+UseConcMarkSweepGC -XX:CMSInitiatingOccupancyFraction=80 -XX:+CMSClassUnloadingEnabled

Survivor大小
- NewSize / ( SurvivorRatio + 2)
- 若是SurvivorRatio =16, NewSize =1280m,那么S大小是70M。
- 过小,溢出的复制Collection进入老生代。
- 太大,闲置无用 浪费内存。
- 使用XX:+PrintTenuringDistribution 和-XX:+PrintGCDetails, -XX:+PrintHeapAtGC观察:
- 与 -XX:+UseAdaptiveSizePolicy 冲突
垃圾回收机制启动
- 垃圾回收机制不会频繁启动,由于机制一旦启动,形成应用程序停顿。
- 机制通常内存剩余5%左右启动,因此有现象:启动服务器,内存不断消耗,有多大内存消耗多大。
- 问题:若是服务器程序频繁触及5%底线,机制频繁启动,形成服务器慢..甚至死机。
- 根源:应用程序无限制频繁大量建立对象,消耗内存。
控制垃圾回收
- 带CMS参数的都是和并发回收相关的
- -XX:+UseParNewGC,对新生代采用多线程并行回收。
- CMSInitiatingOccupancyFraction=90说明年老代到90%满的时候开始执行对年老代的并发垃圾回收(CMS)
- 用jmap和jstack查看
串行 并行回收的区别
- 新生代 高吞吐量:
- -XX:+UseSerialGC
-XX:+UseParallelGC
-XX:+UseParNewGC
- 老生代 低暂停:
- -XX:+UseParallelOldGC
-XX:+UseConcMarkSweepGC
- 相同点:GC时都暂停一切。
- 不一样点:一个线程和多个线程同时GC
并行和CMS(Concurrent-Mark-Sweep)区别
- CMS步骤:
- - initial mark
- concurrent marking
- remark
- concurrent sweeping
- 区别:CMS一个线程,并行多个线程
- CMS只是在1 3阶段暂停,而并行所有暂停。
Parallel GC 和 CMS GC
- 压实compaction是移除内存碎片,也就是移除已经分配的内存之间的空白空间。
- 在Parallel GC中,不管Full GC是否执行,压实老是被执行,会花费更多时间,不过在执行完Full GC后,内存也许再被使用时,会分配得快些,可以顺序分配了。
- CMS GC 并不执行压实,因此更快,碎片太多,没有空间放置大的须要连续空间的对象,“Concurrent mode failure”会发生。
并行和CMS配置
- -XX:UserParNewGC 适合于
新生代 (multiple GC threads)
-XX:+UseConcMarkSweepGC 适合于
老生代 (one GC thread, freezes the JVM only during the initial mark and remark phases)
-XX:InitiatingOccupancyFraction 80是表示CMS是在老生代接近满80%启动,如CPU空闲,可设定点一些。
-XX:+CMSIncrementalMode 用于CMS,不会让处理器Hold住整个并发phases 。
高吞吐量调整
- UseParallelGC 和UseParNewGC等高吞吐量配合参数:
- -XX:+UseAdaptiveSizePolicy
- -XX:MaxGCPauseMillis=… (i.e. 100)
- -XX:GCTimeRatio=… (i.e. 19)
UseAdaptiveSizePolicy
- 当使用-XX:+UseParallelGC 缺省策略加载,XX:+UseAdaptiveSizePolicy。
- 主要调整下面参数,在暂停和吞吐量之间取得一个平衡:
- 一个合适的最大GC暂停值
- 一个合适的应用程序吞吐量值
- 最小化实现路径。
UseAdaptiveSizePolicy 策略路径
- 若是GC暂停时间大于目标暂停时间(-XX:MaxGCPauseMillis=nnn ),下降新生代大小以匹配目标暂停时间。
- 若是暂停时间合适,考虑应用的吞吐量,经过增大新生代的大小知足吞吐量。
- 若是暂停时间和吞吐量两个目标都知足,新生代大小下降以节约成本路径。
UseAdaptiveSizePolicy
- -XX:MaxGCPauseMillis=nnn :不能设置太小,会阻碍吞吐量,若是不设置,暂停时间依赖heap中活动数据量。
- -XX:GCTimeRatio=nnn 不超过应用运行时间的1 / (1 + nnn) 用在垃圾回收上。缺省99。垃圾回收时间不该该超过总体时间的1%
JVM微调调试方法
- 配置JVM的JAVA_OPTS参数 –verbosegc
- 观察Full GC的信息输出:
- [Full GC $before->$after($total), $time secs]
- Full GC太频繁,应用暂停,响应时间受影响。
- 克服GC太频繁方法:
- 1. 增大内存。增大年轻代的内存
- 2.使用LRU等缓存,限制大量对象建立。
- 3. 64位下压缩对象头。
- 消灭Full GC:-XX:+PrintGCDetails 无Full GC输出
内存大小影响
- 大内存:
1. 下降GC执行次数。
2.增长每次GC执行的时间。
- 小内存:
1.增长了GC执行次数
2.下降每次GC执行的时间。
若是Full GC可以在1秒内完成,10G也是合适的。
Jstat 监视微调
- jstat -gcutil 21891 250 7
- 21891是Java的pid, 250表示间隔几秒 7表示采样7次
- S0 S1 E O P YGC YGCT FGC FGCT GCT
12.44 0.00 27.20 9.49 96.70 78 0.176 5 0.495 0.672
12.44 0.00 62.16 9.49 96.70 78 0.176 5 0.495 0.672
12.44 0.00 83.97 9.49 96.70 78 0.176 5 0.495 0.672
0.00 7.74 0.00 9.51 96.70 79 0.177 5 0.495 0.673
0.00 7.74 23.37 9.51 96.70 79 0.177 5 0.495 0.673
0.00 7.74 43.82 9.51 96.70 79 0.177 5 0.495 0.673
0.00 7.74 58.11 9.51 96.71 79 0.177 5 0.495 0.673
- Minor GC :YGC年轻代GC发生了78次,YGCT是GC发生的时间累计0.176。
- FULL GC发生了5次,累计0.495, 每次是0.495/5
- http://www.cubrid.org/blog/dev-platform/how-to-monitor-java-garbage-collection/
- 若是GC执行时间在一秒以上,须要GC微调,若是在0.1-0.3之间则不须要
须要微调的案例
- Full GC超过一秒,须要微调。
- Minor GC正常

微调前检查内存大小分配
- jstat –gccapacity
- NGCMN NGCMX NGC S0C S1C EC OGCMN OGCMX OGC OC PGCMN PGCMX PGC PC YGC FGC
- 212992.0 212992.0 212992.0 21248.0 21248.0 170496.0 1884160.0 1884160.0 1884160.0 1884160.0 262144.0 262144.0 262144.0 262144.0 54 5
- 新生代是 212,992 KB,老生代是1,884,160 KB
- 新生代:老生代是1:9, 调整NewRatio
- NewRatio=2
- NewRatio=3
- NewRatio=4
- 若是其中一个设置没有FULL GC发生,就是合适新生代和老生代的大小。
- 随着新生代内存减少,其GC时间缩短:
- NewRatio=2: 45 ms
- NewRatio=3: 34 ms
- NewRatio=4: 30 ms
- 内存输出结构:
- S0 S1 E O P YGC YGCT FGC FGCT GCT
- 8.61 0.00 30.67 24.62 22.38 2424 30.219 0 0.000 30.219
- 年轻代发生了2424次,而FullGC没有一次发生,存在大量临时对象都是新生代毁灭。
Jstat参数说明

JVM优化参数
- JAVA_OPTS="$JAVA_OPTS -verbose:gc -XX:+PrintGCDetails -XX:+PrintTenuringDistribution -Xloggc:/home/jdon/jdongc.log -server -Xms1536m -Xmx1664m -XX:NewSize=768m -XX:MaxNewSize=896m -XX:+UseAdaptiveGCBoundary -XX:MaxGCPauseMillis=250 -XX:+UseAdaptiveSizePolicy -XX:+DisableExplicitGC -XX:ParallelGCThreads=4 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalMode -XX:CMSInitiatingOccupancyFraction=80 -XX:+CMSClassUnloadingEnabled
- 最大新生代GC暂停时间是250毫秒,在这个基础上JVM自动调整尽可能知足吞吐量。
- [GC 2016.468: [ASParNew: 686991K->42743K(711316K), 0.1310080 secs] 713706K->74888K(1422668K) icms_dc=0 , 0.1324500 secs]
- S0 S1 E O P YGC YGCT FGC FGCT GCT
- 0.00 64.80 93.53 4.52 66.34 7 0.619 7 2.381 3.000

若是响应时间仍是不够快?
- 响应延迟和吞吐量是一对矛盾,而吞吐量主要标志是内存大小。
- 下降NewSize大小, 下降survivor空间。
- 下降进入老生代reduce的门槛,由于缓存Hold住大量长生命对象,让这些对象进口进入老生代。而老生代的CMS不多暂停。
CMS
- CMS并不进行内存压实compact,因此,会致使碎片。而碎片也会致使暂停。
- Apache Cassandra使用 slab allocator。
- 每一个Slab是2M;
- 用compare-and-set拷贝他们。
- 三天
G1 vs CMS vs Parallel GC
三个垃圾回收机制的比较,语法以下:
- -XX:+UseParallelOldGC
- -XX:+UseConcMarkSweepGC
- -XX:+UseG1GC
使用GCViewer观察,结果以下 单位是毫秒:
|
Parallel |
CMS |
G1 |
Total GC pauses |
20 930 |
18 870 |
62 000 |
Max GC pause |
721 |
64 |
50 |
并行GC ( - XX:+ UseParallelOldGC ) 。在30多分钟的测试时间内,用并行收集器 GC暂停花了接近21秒。最长停顿了721毫秒。所以,咱们以此为基准: GC下降了吞吐量为总运行时间的1.1 %。最坏状况下的延迟为721ms 。
CMS ( - XX:+ UseConcMarkSweepGC ) 一样30分钟,由于GC咱们失去了不到19秒 。相比并行GC吞吐量明智的。延迟另外一方面已显著改善 - 最坏状况下的延迟时间减小10倍以上!来自GC的最大暂停时间咱们如今面临的只是64ms。
最闪亮的GC算法 - G1 ( - XX:+ UseG1GC ) 。在一样的测试,,咱们看到的结果吞吐量问题比较严重。这一次,咱们的应用程序花费超过一分钟等待GC来完成。比较这与CMS中的开销只有1% ,咱们如今面对接近的吞吐量3.5 %的效果。但若是你真的不关心的吞吐量,而是想挤出最后那么一点延迟 - 比CMS提升20%左右 - 用G1看到的最长GC暂停服用仅50毫秒。
结论:CMS仍然是最好的“默认”选项。 G1的吞吐量仍然差那么多,获得的延迟一般是不值得的。
JVM推荐设置
Heap size |
Total GC pauses GC暂停 |
Throughput吞吐量 |
-Xmx300m |
207.48s |
92.25% |
-Xmx384m |
54.13s |
97.97% |
-Xmx720m |
20.52s |
99.11% |
-Xmx1,440m |
*11.37s |
*99.55%
|
对象和Java多线程
- 缺省对象都是继承java.lang.Object
- 也能够特别继承java.lang.Thread ;
- 或实现java.lang.Runnable接口
- 对象的方法以线程方式执行。
线程的主内存和工做内存
- 主内存对于全部线程可见的。主内存通常在Heap中,对象的属性值是放在Heap中。
- 每条线程都有本身的工做内存。工做内存里的变量,在多核处理器下,将大部分储存于处理器高速缓存中。
- 工做内存会拷贝主存中的变量,而后对变量的操做在本身的工做内存中进行。
- 线程之间没法相互直接访问,变量传递均须要经过主存完成。
问题?

如何保证内存计算一致性
- 缓存一致性
当一个线程更新了本身工做内存中的数据后,没有写到主内存,其余线程是不知道的。
(1)顺序一致性模型:
要求对改变的值当即进行传播, 并确保该值被全部其余线程接受后, 才能继续执行其余指令.
(2) 释放一致性模型:
容许线程将改变的值延迟到锁释放时才进行传播.
happens-before ordering

- 1.获取对象监视器的锁(lock)
- 2. 清空工做内存数据, 从主存复制变量到当前工做内存, 即同步数据 (read and load)
- 3. 执行代码,改变共享变量值 (use and assign)
- 4. 将工做内存数据刷回主存 (store and write)
- 5. 释放对象监视器的锁 (unlock)
happens-before ordering实现
- final 永不改变
- volatile 标注被改变的值为原子性
- JVM优化的锁java.util.concurrent.locks包java.util.concurrent.atmoic包
- synchronized 堵塞锁
- 如何选用这些工具呢?前提是保证线程安全性。
线程安全模式
- 线程安全性的定义要求不管是多线程中的时序或交替操做,都要保证不破坏业务自己不变约束 。
- 为了保护状态的一致性,要在单一的原子操做中更新相互关联的状态变量。
- 设计线程安全的类时,优秀的面向对象技术——封装、不可变性以及明确的不变约束——会给你提供诸多的帮助。
- 无状态对象永远是线程安全的
线程安全模式
- 尽可能不使用synchronized锁,锁是耗费资源和性能的。
- 首先 编写那些不用任何特别处理的线程安全代码,好比不变性代码。
- 使用producer-observer模式。
- 其次:使用Visibility 使资料对全部线程可见。
- 最后:使用JVM优化的锁。
单值更新
- 使用Atmoic原子特性API:
- Atomic{Integer|Long}.compareAndSet().
- 使用CAS实现机制的API。
- AtomicReference.compareAndSet()实现不变性对象内部的组合更新。
immutable 不可变模式
- Immutable是当被构造之后就再也不改变。
- Immutable 的对象老是线程安全。
- 特征:
- 1. 构造之后就不会改变;
- 2. 全部字段是 final;
- 3. 它是正常构造。
- 发布一个Immutable对象是安全的。
Publishing发布公开对象
- public static Set<Secret> knownSecrets;
- public void initialize() {
- knownSecrets = new HashSet<Secret>();
- }
- 因为外界能够访问knownSecrets 而且修改,那么knownSecrets 至关于脱离当前对象的scope生命周期,变成escaped 逃脱了。
安全的发布公开对象模式
- 发布表明:引用这个对象而且这个对象中状态必须同时为其余人可见的,经过以下方式发布:
- 1.从一个静态初始期中赋予一个对象引用;
- public static Holder holder = new Holder(42);
- 2. 将引用赋予一个 volatile 或者 AtomicReference字段;
- 3. 将引用赋予一个 final字段,而且构造后不改变(不变性); or
- 4.将引用赋予一个 字段被适当的锁守卫。
自然的线程安全
- Hashtable, synchronizedMap, or Concurrent-Map
- Vector, CopyOnWriteArrayList, CopyOnWrite-ArraySet, synchronizedList, or synchronizedSet
- BlockingQueue or a ConcurrentLinkedQueue
Visibility/NoVisibility模式
- 线程更新的是本身私有空间变量,须要更新到主内存空间,一个线程修改的结果对于另一个线程是NoVisibility :
- class RealTimeClock {
- private int clkID;
- public int clockID() { return clkID; }
- public void setClockID(int id) { clkID = id; } }
- Thread 1 calls the setClockID method, passing a value of 5.
- Thread 2 calls the setClockID method, passing a value of 10.
- Thread 1 calls the clockID method, which returns the value 5.
- 出现明明修改某个字段值,可是刷新仍是旧值。
多线程访问同一资源
- 1. 使用synchronized
- 2. 将clkID 标为volatile
- 使用synchronized 坏处:排他性锁定,影响性能。
- 使用JDK5 ReentrantReadWriteLock
volatile
- 不是很健壮的锁机制,适合必定条件:
- 1. 写变量值不依赖它当前值,好比:直接this.xxx = xxx;包括volatile bean
- 2.这个变量不参与其余变量的不变性范围。
- 做为标识完成、中断、状态的标记使用
- 加锁能够保证可见性与原子性(“读-改-写” 原子操做 );volatile变量只能保证可见性。
- 相关文章:http://www.ibm.com/developerworks/java/library/j-jtp06197.html
Volatile缺点
- @NotThreadSafe
- public class NumberRange {
- private int lower, upper;
- public int getLower() { return lower; }
- public int getUpper() { return upper; }
- public void setLower(int value) {
- if (value > upper) throw new IllegalArgumentException(...);
- lower = value; }
- public void setUpper(int value) {
- if (value < lower) throw new IllegalArgumentException(...);
- upper = value; } }
Volatile缺点
- 初始值是(0, 5)
- 线程A: setLower(4)
- 线程B: setUpper(3)
- 结果是 (4, 3) , 而这一结果根据setLower和setUpper中if逻辑判断是不可能获得的。
- 这时须要synchronization
- 或使用final替代Volatile
使用final替代Volatile
- 若是须要修改,更换整个对象,值对象定义

原子操做模式
- 只是将变量做为可见仍是不够,还要对操做这些变量的操做方法保证原子性。
- 假设有操做A和B,若是从执行A的线程的角度看,当其余线程执行B时,要么B所有执行完成,要么一点都没有执行,这样A和B互为原子操做。一个原子操做是指:该操做对于全部的操做,包括它本身,都知足前面描述的状态。
- 为了确保线程安全,“检查再运行”操做(如惰性初始化)和读-改-写操做(如自增)必须是原子操做。咱们将“检查再运行”和读-改-写操做的所有执行过程看做是复合操做:为了保证线程安全,操做必须原子地执行。
锁模式
- synchronized块:内部锁(intrinsic locks)或监视器锁(monitor locks)
- 执行线程进入synchronized块以前会自动得到锁。
- 进入这个内部锁保护的同步块或方法。
- 内部锁在Java中扮演了互斥锁 。意味着至多只有一个线程能够拥有锁,可能发生死锁,执行synchronized块的线程,不可能看到会有其余线程能同时执行由同一个锁保护的synchronized块。
- 它彻底禁止多个用户同时使用 ,性能问题
重进入(Reentrancy)
- 当一个线程请求其余线程已经占有的锁时,请求线程将被阻塞 。线程在试图得到它本身占有的锁时,请求会成功 .
- public class Widget {
- public synchronized void doSomething() {
- }}
- public class LoggingWidget extends Widget {
- public synchronized void doSomething() {
- System.out.println(toString() + ": calling doSomething");
- super.doSomething();
- }}
Reentrancy好处
- 子类覆写了父类synchronized类型的方法,并调用父类中的方法。若是没有可重入的锁,这段看上去很天然的代码就会产生死锁。
cheap read-write lock
- public class CheesyCounter {
- // Employs the cheap read-write lock trick // All mutative operations MUST be done with the 'this' lock held @GuardedBy("this")
- private volatile int value;
- public int getValue() { return value; }
- public synchronized int increment() {
- return value++;
- }
- }
ReentrantReadWriteLock
- 适合场景:大量并发读操做,少许甚至一个线程作修改。
- 优势:克服synchronization跨多个方法没法重入的问题(容易发生死锁),好比 在一个地方lock,而在另一个地方 unlock.
- public void set(String key, String value) {
- write.lock();
- try {dictionary.put(key, value);}
- finally {write.unlock();}
- }
- public String get(String key) {
- read.lock();
- try {return dictionary.get(key);}
- finally {read.unlock();}
- }
什么时候用
- 若是须要timed, polled, 或可中断 lock, fair queueing, non-block-structured locking.就是要ReentrantReadWriteLock
- 不然使用 synchronized.
案例:如何实现集合的边读边改
- 联系人名单集合,发送Email
- public void sendMessages(Map contactMap) {
- sendEmail(contactMap.values());
- }
- contactMap是Contact集合,contactMap.values是遍历contactMap中元素Contact对象。
- 假设:若是在遍历发生Email同时,有新的Contact对象加入到contactMap集合中,这时会抛出并发错误。
设计新的不可变集合

使用新不可变集合类

状态和值对象
- 值对象是DDD中一种模型,不可变性。
- 状态是表达一段时间内一个逻辑为真的事实,状态是不可变的,由于咱们不能回到过去改变状态。
- 状态是一种值对象。
- 经过不变性规避了共享锁的争夺,从而得到了更好的并发性能。
- 具体案例见jivejdon中的ForumState等
ThreadLocal
- ThreadLocal能够维持线程的封闭性,一个请求一个线程,至关于request.setAttribute/getAttribute;
- ThreadLocal能够为每一个线程保存本身的状态值。
- ThreadLocal的get/set方法为每一个线程维持一份独立的分离的状态值。Get方法可以返回最新经过set方法保存的状态值
- 常常被框架使用。如Spring Hibernate
数据库链接放入ThreadLocal
- private static ThreadLocal<Connection> connectionHolder =
- new ThreadLocal<Connection>() {
- public Connection initialValue() {
- return DriverManager.getConnection(DB_URL); }
- } ;
- public static Connection getConnection() {
- return connectionHolder.get();
- }