JVM性能优化二:线程锁优化

 上页

对象和Java多线程

  1. 缺省对象都是继承java.lang.Object
  1. 也可以特别继承java.lang.Thread ;

 

  1. 或实现java.lang.Runnable接口
  1. 对象的方法以线程方式执行。

 

线程的主内存和工作内存

  1. 主内存对于所有线程可见的。主内存一般在Heap中,对象的属性值是放在Heap中。
  2. 每条线程都有自己的工作内存。工作内存里的变量,在多核处理器下,将大部分储存于处理器高速缓存中。
  3. 工作内存会拷贝主存中的变量,然后对变量的操作在自己的工作内存中进行。
  4. 线程之间无法相互直接访问,变量传递均需要通过主存完成。

 

问题?

多线程
如何保证内存计算一致性

  1. 缓存一致性

   当一个线程更新了自己工作内存中的数据后,没有写到主内存,其他线程是不知道的。
  (1)顺序一致性模型:
要求对改变的值立即进行传播, 并确保该值被所有其他线程接受后, 才能继续执行其他指令.
  (2) 释放一致性模型:
   允许线程将改变的值延迟到锁释放时才进行传播.


 

happens-before ordering

threads

  1. 1.获取对象监视器的锁(lock)
  2. 2. 清空工作内存数据, 从主存复制变量到当前工作内存, 即同步数据 (read and load)
  3. 3. 执行代码,改变共享变量值 (use and assign)
  4. 4. 将工作内存数据刷回主存 (store and write)
  5. 5. 释放对象监视器的锁 (unlock)

happens-before ordering实现

  1. final    永不改变
  2. volatile 标注被改变的值为原子性
  3. JVM优化的锁java.util.concurrent.locks包java.util.concurrent.atmoic包
  4. synchronized  堵塞锁
  1. 如何选用这些工具呢?前提是保证线程安全性。

 

线程安全模式

  1. 线程安全性的定义要求无论是多线程中的时序或交替操作,都要保证不破坏业务本身不变约束 。
  2. 为了保护状态的一致性,要在单一的原子操作中更新相互关联的状态变量。
  3. 设计线程安全的类时,优秀的面向对象技术——封装、不可变性以及明确的不变约束——会给你提供诸多的帮助。 
  4. 无状态对象永远是线程安全的

线程安全模式

  1. 尽量不使用synchronized锁,锁是耗费资源和性能的。
  1. 首先 编写那些不用任何特别处理的线程安全代码,比如不变性代码。
  1. 使用producer-observer模式。
  1. 其次:使用Visibility 使资料对所有线程可见。
  1. 最后:使用JVM优化的锁。

单值更新

  1. 使用Atmoic原子特性API:
  2. Atomic{Integer|Long}.compareAndSet().
  1. 使用CAS实现机制的API。
  1. AtomicReference.compareAndSet()实现不变性对象内部的组合更新。

immutable 不可变模式

  1. Immutable是当被构造以后就不再改变。
  2. Immutable 的对象总是线程安全。
  3. 特征:
  4. 1. 构造以后就不会改变;
  5. 2. 所有字段是 final;
  6. 3. 它是正常构造。
  7. 发布一个Immutable对象是安全的。

Publishing发布公开对象

  1. public static Set<Secret> knownSecrets;
  2. public void initialize() {
  3.     knownSecrets = new HashSet<Secret>();
  4. }
  5. 由于外界可以访问knownSecrets 并且修改,那么knownSecrets 相当于脱离当前对象的scope生命周期,变成escaped 逃脱了。

安全的发布公开对象模式

  1. 发布代表:引用这个对象并且这个对象中状态必须同时为其他人可见的,通过如下方式发布:
  2. 1.从一个静态初始期中赋予一个对象引用;
  3. public static Holder holder = new Holder(42);
  4. 2. 将引用赋予一个 volatile 或者 AtomicReference字段;
  5. 3. 将引用赋予一个 final字段,并且构造后不改变(不变性); or
  6. 4.将引用赋予一个 字段被适当的锁守卫。

天然的线程安全

  1. Hashtable, synchronizedMap, or Concurrent-Map
  2. Vector, CopyOnWriteArrayList, CopyOnWrite-ArraySet, synchronizedList, or synchronizedSet
  3. BlockingQueue or a ConcurrentLinkedQueue

 

Visibility/NoVisibility模式

  1. 线程更新的是自己私有空间变量,需要更新到主内存空间,一个线程修改的结果对于另外一个线程是NoVisibility :
  2. class RealTimeClock {
  3.    private int clkID;
  4.     public int clockID() { return clkID; }
  5.     public void setClockID(int id) { clkID = id; } }
  1. Thread 1 calls the setClockID method, passing a value of 5.
  2. Thread 2 calls the setClockID method, passing a value of 10.
  3. Thread 1 calls the clockID method, which returns the value 5.
  4. 出现明明修改某个字段值,但是刷新还是旧值。

多线程访问同一资源

  1. 1. 使用synchronized
  2. 2. 将clkID 标为volatile
  1. 使用synchronized 坏处:排他性锁定,影响性能。
  1. 使用JDK5 ReentrantReadWriteLock

 

volatile

  1. 不是很健壮的锁机制,适合一定条件:
  2. 1. 写变量值不依赖它当前值,比如:直接this.xxx = xxx;包括volatile bean
  3. 2.这个变量不参与其他变量的不变性范围。
  4. 作为标识完成、中断、状态的标记使用
  5. 加锁可以保证可见性与原子性(“读-改-写” 原子操作 );volatile变量只能保证可见性。
  6. 相关文章:http://www.ibm.com/developerworks/java/library/j-jtp06197.html

Volatile缺点

  1. @NotThreadSafe
  2. public class NumberRange {
  3. private int lower, upper;
  4. public int getLower() { return lower; }
  5. public int getUpper() { return upper; }
  6. public void setLower(int value) {
  7.     if (value > upper) throw new         IllegalArgumentException(...);
  8.    lower = value; }
  9. public void setUpper(int value) {
  10.    if (value < lower) throw new IllegalArgumentException(...);  
  11.         upper = value; } }

Volatile缺点

  1. 初始值是(0, 5)
  2. 线程A: setLower(4)
  3. 线程B: setUpper(3)
  4. 结果是 (4, 3) , 而这一结果根据setLower和setUpper中if逻辑判断是不可能得到的。
  5. 这时需要synchronization
  6. 或使用final替代Volatile

 

使用final替代Volatile

  1. 如果需要修改,更换整个对象,值对象定义

final

原子操作模式

  1. 只是将变量作为可见还是不够,还要对操作这些变量的操作方法保证原子性。
  2. 假设有操作AB,如果从执行A的线程的角度看,当其他线程执行B时,要么B全部执行完成,要么一点都没有执行,这样AB互为原子操作。一个原子操作是指:该操作对于所有的操作,包括它自己,都满足前面描述的状态。
  3. 为了确保线程安全,“检查再运行”操作(如惰性初始化)和读-改-写操作(如自增)必须是原子操作。我们将“检查再运行”和读-改-写操作的全部执行过程看作是复合操作:为了保证线程安全,操作必须原子地执行。

 

锁模式

  1. synchronized块:内部锁(intrinsic locks)或监视器锁(monitor locks)
  2. 执行线程进入synchronized块之前会自动获得锁。
  3. 进入这个内部锁保护的同步块或方法。
  4. 内部锁在Java中扮演了互斥锁 。意味着至多只有一个线程可以拥有锁,可能发生死锁,执行synchronized块的线程,不可能看到会有其他线程能同时执行由同一个锁保护的synchronized块。
  5. 它完全禁止多个用户同时使用 ,性能问题

重进入(Reentrancy)

  1. 当一个线程请求其他线程已经占有的锁时,请求线程将被阻塞 。线程在试图获得它自己占有的锁时,请求会成功 .
  2. public class Widget {
  3.     public synchronized void doSomething() {
  4.     }}
  5. public class LoggingWidget extends Widget {
  6.     public synchronized void doSomething() {
  7.         System.out.println(toString() + ": calling doSomething");
  8.         super.doSomething();
  9.     }}

Reentrancy好处

  1. 子类覆写了父类synchronized类型的方法,并调用父类中的方法。如果没有可重入的锁,这段看上去很自然的代码就会产生死锁。

 

cheap read-write lock

  1. public class CheesyCounter {
  2. // Employs the cheap read-write lock trick // All mutative operations MUST be done with the 'this' lock held @GuardedBy("this")
  3. private volatile int value;
  4. public int getValue() { return value; }
  5. public synchronized int increment() {
  6.          return value++;
  7. }
  8. }

ReentrantReadWriteLock

  1. 适合场景:大量并发读操作,少量甚至一个线程做修改。
  2. 优点:克服synchronization跨多个方法无法重入的问题(容易发生死锁),比如 在一个地方lock,而在另外一个地方 unlock.
  3. public void set(String key, String value) {
  4. write.lock();
  5. try {dictionary.put(key, value);}
  6. finally {write.unlock();}
  7. }
  8. public String get(String key) {
  9. read.lock();
  10. try {return dictionary.get(key);}
  11. finally {read.unlock();}
  12. }

何时用

  1. 如果需要timed, polled, 或可中断 lock, fair queueing,  non-block-structured locking.就是要ReentrantReadWriteLock
  1. 否则使用 synchronized.

 

案例:如何实现集合的边读边改

  1. 联系人名单集合,发送Email
  2. public void sendMessages(Map contactMap) {
  3.     sendEmail(contactMap.values());
  4. }
  5. contactMap是Contact集合,contactMap.values是遍历contactMap中元素Contact对象。
  6. 假设:如果在遍历发生Email同时,有新的Contact对象加入到contactMap集合中,这时会抛出并发错误。

设计新的不可变集合

不可变集合
使用新不可变集合类

jvm



状态和值对象

  1. 值对象是DDD中一种模型,不可变性。
  2. 状态是表达一段时间内一个逻辑为真的事实,状态是不可变的,因为我们不能回到过去改变状态。
  3. 状态是一种值对象。
  4. 通过不变性规避了共享锁的争夺,从而获得了更好的并发性能。
  5. 具体案例见jivejdon中的ForumState等

 

ThreadLocal

  1. ThreadLocal可以维持线程的封闭性,一个请求一个线程,相当于request.setAttribute/getAttribute;
  2. ThreadLocal可以为每个线程保存自己的状态值。
  3. ThreadLocal的get/set方法为每个线程维持一份独立的分离的状态值。Get方法能够返回最新通过set方法保存的状态值
  4. 经常被框架使用。如Spring Hibernate

数据库连接放入ThreadLocal

  1. private static ThreadLocal<Connection> connectionHolder =
  2.     new ThreadLocal<Connection>() {
  3.          public Connection initialValue() {
  4.                 return DriverManager.getConnection(DB_URL); }
  5. } ;
  6. public static Connection getConnection() {
  7.         return   connectionHolder.get();
  8. }

 

 


 

首页

JVM有关垃圾回收机制的配置

服务器后端性能大比拼

多线程

同步或锁

性能测试

性能调优

高性能

可伸缩性

生产环节下JVM运营监测

JVM专题讨论

 

 

 

猜你喜欢