作者微信 bishe2022

代码功能演示视频在页面下方,请先观看;如需定制开发,联系页面右侧客服
JVM性能优化二:线程锁优化

Custom Tab

对象和Java多线程

  1. 缺省对象都是继承java.lang.Object

  1. 也可以特别继承java.lang.Thread ;

 

  1. 或实现java.lang.Runnable接口

  1. 对象的方法以线程方式执行。

线程的主内存和工作内存

  1. 主内存对于所有线程可见的。主内存一般在Heap中,对象的属性值是放在Heap中。

  2. 每条线程都有自己的工作内存。工作内存里的变量,在多核处理器下,将大部分储存于处理器高速缓存中。

  3. 工作内存会拷贝主存中的变量,然后对变量的操作在自己的工作内存中进行。

  4. 线程之间无法相互直接访问,变量传递均需要通过主存完成。

    问题?

jvm7.png




如何保证内存计算一致性

  1. 缓存一致性

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

ssssssssssssssssssssssssssssssssssss.png

  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.将引用赋予一个 字段被适当的锁守卫。

天然的线程安全

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


Visibility/NoVisibility模式

  1. 线程更新的是自己私有空间变量,需要更新到主内存空间,一个线程修改的结果对于另外一个线程是NoVisibility :

  1. class RealTimeClock {

  2.    private int clkID;

  3.     public int clockID() { return clkID; }

  4.     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缺点

@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缺点

  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. 如果需要修改,更换整个对象,值对象定义

qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq.png

原子操作模式

  1. 只是将变量作为可见还是不够,还要对操作这些变量的操作方法保证原子性。

  2. 假设有操作AB,如果从执行A的线程的角度看,当其他线程执行B时,要么B全部执行完成,要么一点都没有执行,这样AB互为原子操作。一个原子操作是指:该操作对于所有的操作,包括它自己,都满足前面描述的状态。

  3. 为了确保线程安全,“检查再运行”操作(如惰性初始化)和读-改-写操作(如自增)必须是原子操作。我们将“检查再运行”和读-改-写操作的全部执行过程看作是复合操作:为了保证线程安全,操作必须原子地执行。


 

锁模式

  1. synchronized块:内部锁(intrinsic locks)或监视器锁(monitor locks)

  2. 执行线程进入synchronized块之前会自动获得锁。

  3. 进入这个内部锁保护的同步块或方法。

  4. 内部锁在Java中扮演了互斥锁 。意味着至多只有一个线程可以拥有锁,可能发生死锁,执行synchronized块的线程,不可能看到会有其他线程能同时执行由同一个锁保护的synchronized块。

  5. 它完全禁止多个用户同时使用 ,性能问题

重进入(Reentrancy)

  1. 当一个线程请求其他线程已经占有的锁时,请求线程将被阻塞 。线程在试图获得它自己占有的锁时,请求会成功 .

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好处

  1. 子类覆写了父类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();}
}

何时用

  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集合中,这时会抛出并发错误。

设计新的不可变集合

rrrrrrrrrrrrrrrrr.png

使用新不可变集合类

tttttt.png

状态和值对象

  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

private static ThreadLocal<Connection> connectionHolder =
    new ThreadLocal<Connection>() {
         public Connection initialValue() {
                return DriverManager.getConnection(DB_URL); }
} ;
public static Connection getConnection() {
        return   connectionHolder.get();
}

转载自:http://www.jdon.com/idea/jvm3.html









Home