对象和Java多线程
缺省对象都是继承java.lang.Object
也可以特别继承java.lang.Thread ;
或实现java.lang.Runnable接口
对象的方法以线程方式执行。
线程的主内存和工作内存
主内存对于所有线程可见的。主内存一般在Heap中,对象的属性值是放在Heap中。
每条线程都有自己的工作内存。工作内存里的变量,在多核处理器下,将大部分储存于处理器高速缓存中。
工作内存会拷贝主存中的变量,然后对变量的操作在自己的工作内存中进行。
线程之间无法相互直接访问,变量传递均需要通过主存完成。
问题?
如何保证内存计算一致性
缓存一致性
当一个线程更新了自己工作内存中的数据后,没有写到主内存,其他线程是不知道的。
(1)顺序一致性模型:
要求对改变的值立即进行传播, 并确保该值被所有其他线程接受后, 才能继续执行其他指令.
(2) 释放一致性模型:
允许线程将改变的值延迟到锁释放时才进行传播.
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(); }
转载自:http://www.jdon.com/idea/jvm3.html