多线程摘录 001

需要考虑什么?
* 正确性. 程序必须能得到正确的结果
* 生存性. 程序必须能稳定有效运行
* 安全性. 例如不造成资源共享问题等...
* 性能.

Timer: 它自己管理了线程来处理TimerTask.

使线程安全运行的一些方法:
* 不共享内部状态, 如使用无状态的设计
* 使用不变模式
* 使用同步

线程对资源使用的原子性:
如果线程两次访问资源, 得到的不是相同的状态, 就有原子性的问题.
通常线程对共享资源的竞争(如没有同步, 混合操作set,get等)会造成原子性问题.
设计良好的类也可能因为继承而造成原子性问题, 因为子类可能没有遵守父类处理资源状态的规则
单例模式中, 如果用的是延迟加载的模式, 也有原子性问题.

JDK5引入了新的并发机制, 其基本思想仍然是同步, 但是具体的做法是最大限度的缩小使用同步的粒度, 使得同步只发生在共享资源内部, 比如AtomicLong, 由JVM保证了不会对其进行影子操作(如两线程同时访问增加操作,保证两个增加操作是串行的. 这些机制相当于volatile语义上的等价物, 只是更适合应用.

锁:
锁是实现同步的一种手段, 保证资源访问的独占性. 有乐观锁和悲观锁.

JDK提供的synchronized关键字是可重入的锁定, 即, 当占据了资源的线程释放了资源后, 所有在等待该资源的线程重新竞争以获取对资源的锁定.

提高性能:
如果原来的同步是方法层面的或者很大一块同步, 那么考虑把这个大范围的同步拆分成几个小范围的同步, 也是提供性能的一个方式, 因为拆分后, 不同的线程可以在不同的同步块内执行, 是"半并发"的形式.

考虑资源状态的可见性, 是否产生过期数据
(32位操作系统上的?) 64位数据操作可能出现非原子性操作, 即对64位数据(long,double)要进行高低32位操作, 中间的间隔就可能出现问题

什么时候使用volatile变量.

不好翻译, 摘出原文来
Use volatile variables only when they simplify implementing and verifying your synchronization policy; avoid using volatile variables when veryfing correctness would require subtle reasoning about visibility. Good uses of volatile variables include ensuring the visibility of their own state, that of the object they refer to, or indicating that an important lifecycle event (such as initialization or shutdown) has occurred.

只有在简化实现和验证同步策略的时候才应该使用volatile, 如果验证正确性需要某种莫名其妙的关于可见性的理由, 就要避免使用volatile. 对volatile变量的正确使用, 包括保证引用到的对象正确的状态, 或者是指明一种重要的生命周期事件(如初始化和关闭)的发生.

下面是一个正确使用volatile的例子:
volatile
boolean asleep;
...
while (!asleep) {
   countSomeSheep();
}
因为这里没有使用同步(synchronized), 如果没有加上volatile, 那么, 就有可能线程A在第一个时刻修改了asleep的值, 然而仍没有写入主内存的时刻, 线程B访问了asleep, 取得的是旧的值. 【注:此处应参考java内存模型】.

状态泄漏
公布一个对象或者状态意味着把信息传递到一个线程可触及的范围之外,同时也可能造成内部状态"不经意"的泄漏出去. 包括引用到的内部对象, this等. 这些方式如:
1) return new EventListener(this);
2) private String[] status; public String[] getStatus(){return status;}
3) source.registerListener(new EventListener(){ //内部匿名类隐含了this
        public void onEvent(Event e){ dosomething(e); }
    }
不过应该考虑这些"泄漏"是否真的会有影响. 对于匿名内部类的问题, 如果要禁止this被意外引用, 可以定义为局部变量即可

避免资源共享冲突: 锁住资源

比如Swing的控件, 全部被锁定在event dispatch线程范围内, 比如JDBC连接池, 通常它的客户端只是单个线程处理单个请求, 因为只要连接池不在某个连接被申请后再分配给其他线程, 就是安全的.
局部变量是线程安全的, 因为只能在线程的方法栈的范围内能引用到; 也可以考虑使用ThreadLocal, 因为ThreadLocal是绑定到线程的临时存储区.

采用不变模式的设计
如果对象从初始化之后就是保持不变的状态, 无疑是线程安全的. 比如只有构造函数和getter, 没有setter的类. 当然这只是个大致方向, 如果getter返回了对象的引用, 能被随意修改, 这个类也不能称为不变了. 又或者使用final关键字

技巧分析: 只使用volatile来实现线程安全

public class VolatileCachedFactorizer implements Servlet {
    //
注: OneValueCache 是一个不变模式的对象, 是通过copy构造参数的方式来实现的
    private volatile OneValueCache cache =
        new OneValueCache(null, null);

    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = cache.getFactors(i);
        if (factors == null) {
            factors = factor(i); //
factor(i)是一个private方法.
            cache = new OneValueCache(i, factors);
        }
        encodeIntoResponse(resp, factors);
    }
}

代码不多, 主要目的是把最后一次结果缓存起来. 因为factors = factor(i);是在一个线程的范围内单独完成的, factors不会被其他线程引用到, cache = new OneValueCache(i, factors); 这一行, 是copy了factors来构造OneValueCache, 并且cache变量是volatile, 一旦OneValueCache对象构造完毕, JVM会马上把它写入主内存, 确保中间写入过程的原子性.

安全发布对象的流程
1) 使用静态初始化的方式来初始化对象
2) 把该对象赋予volatile变量或者AtomicReference
3) 或者把该对象赋予final变量
4) 或者把该对象赋予由锁定确保安全性的变量. 如一些容器类(Vector...)

一些集合框架提供的线程安全的类:

* Hashtable,
synchronizedMap,ConcurrentMap
*
Vector, CopyOnWriteArrayList, CopyOnWriteArraySet, synchronizedList, synchronizedSet
* BlockingQueue, ConcurrentLinkedQueue

什么样的策略是
线程并发安全中最有用的?
1) 限制对象于线程范围内
2) 只读共享
3) 安全共享. 被共享对象有内部的线程安全机制
4) 锁定保护


转自http://hi.baidu.com/iwishyou2/blog/index/2

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值