励精图治---Concurrency---组合对象

来源:互联网 发布:淘宝清洗 编辑:程序博客网 时间:2024/05/21 09:01

加一句:委托才是解决线程安全最有效的策略。

用现有的线程安全类去管理状态变量。


判断一个类是否是线程安全的?要看三方面。

1. 查看类中所有的状态变量

2. 找出这些状态变量的约束条件,可变的,不可变的

3. 查看,是否对这些状态变量有并发访问管理。这里要注意所加的锁是怎样的。private this 又或者其他。


观点总结

状态变量是越少越简单。需要判断的状态,组合状态也就少了。复杂度肯定下降。

对现有的状态变量,怎么做?

1. 熟悉其特定的属性,比如一定在某个range之内。比如计数器t. 他一定不是负数。

2. 熟悉其后续要验证的状态

3. 这些状态变量是否有牵连?是否会组合使用?要确保其原子性。复合操作也要是具有原子性

4. 要注意一些先决条件,一些判断条件,等待,这伴随着锁的释放和请求,要谨慎。

5. 对象可能会包含 多个对象,多个域,对这些对象的操作,是否是线程安全的。要注意其原子性跟内存可见性。

6. 在C++操作中,我们会考虑析构。这个内存回收问题在java中被稀释。但依旧需要注意,对象在传递的时候,关于这个对象的引用是否被传递,如果publish了被共享的变量或者可变的变量,那么又将涉及访问控制的问题


将状态变量封装

将状态变量封装在对象内部,就可以将数据的访问限制在对象的方法上,这样就更加容易管控。

java中,有很多设计模式,比如观察者模式。工厂模式都可以将数据的访问控制在对象中。

限制了锁的被访问区域,也就杜绝了锁的被意外获得。而这种意外获得可能会造成吞吐量问题,同时,一旦出现问题,也可以将问题锁定在对象内,而不是整个程序。

private class PrivateLock {

private final Object mLock = new Object();//#这个锁是私有的。也就无法被外部类访问

void doSomething() {

synchronized(mLock) {

................

}

}

}


活用现成的线程安全类

当我们从头开始构建一个类,或者多线程不安全类组合成一个类,java的观察者模式就可以解决这个问题。

将线程不安全的类添加封装,封装进线程安全类。

在set get的过程中,我们会出现线程安全问题,但是如果这个过程是通过ConcurrentHashMap之类线程安全的类呢。问题迎刃而解。


多状态变量的组合

public class NumberRanger {

private final AtomicInteger lower = new AtomicInteger(0);

private final AtomicInteger upper = new AtomicInteger(0);

public void setLower(int i ){

if(i > upper.get()) throw IllegalArgumentException ("can't set lower to " + i + " > upper");

lower.set(i);

}


public void setUpper(int i ){

if(i < lower.get()) throw IllegalArgumentExdeption (can't set upper to " + i + " < lower");

upper.set(i);

}


public boolean is InRange(int i){

return (i >= lower.get() && i <=upper.get());

}

}

setLower跟setUpper单独看具有原子性,但是这两者是有关联的。假定当前值(2, 8), 设值成(6, 5),在正常情况下判断,6<8 ,判断通过, 5大于2 判断通过,于是,如果这个时候多线程执行时序出错,那么,就可以设置成(6, 5),这显然是错误的。


怎么解决呢?

多个独立的状态变量组合使用,要将这个复合操作委托给状态变量。

可以额外添加private final Object myLock =new Object();

public class NumberRanger {

private final AtomicInteger lower = new AtomicInteger(0);

private final AtomicInteger upper = new AtomicInteger(0);


private final Object myLock =new Object();

public void setLower(int i ){

synchronized(myLock){

if(i > upper.get()) throw IllegalArgumentException ("can't set lower to " + i + " > upper");

lower.set(i);

}

}


public void setUpper(int i ){

synchronized(myLock){

if(i < lower.get()) throw IllegalArgumentExdeption (can't set upper to " + i + " < lower");

upper.set(i);

}

}


public boolean is InRange(int i){

synchronized(myLock){

return (i >= lower.get() && i <=upper.get());

}

}

}

这里将多个独立的状态变量,委托给了myLock这个状态变量。


线程安全类如何添加新功能?

步骤:

1. 要注意类的锁有几个

2. 这些锁是否跟你要添加的功能有关系

3. 找对你要添加的功能相关的锁


public class ListHelper<E>{

public List<E> list = Collections.synchronizedList(new ArrayList<E>());

........

public synchronized boolean putIfAbsent(E x) {

boolean absent = !list.contains(x);

if(absent) list.add(x);

return absent;

}

}

这段代码是错误的。原因在于,要添加的新功能是 无则添加。list是一个线程安全的类,ListHelper实际上是对list的一种封装。

这里的synchronized是对ListHelper进行的同步,这里的对象是this,即ListHelper。但是实际上list之所以安全是因为在list内部有锁。那么这里锁的对象就错了。

修改如下:

public class ListHelper<E>{

public List<E> list = Collections.synchronizedList(new ArrayList<E>());

........

public boolean putIfAbsent(E x) {

synchronized(list){

boolean absent = !list.contains(x);

if(absent) list.add(x);

return absent;

}

}

}

锁的对象改成list。


封装---将线程非安全类整成线程安全类

通过实现一个线程非安全类,将其接口封装进当前类中,而将当前类设计成线程安全的。通过this锁增加一层额外的锁。使得这个线程非安全类变得安全。



文档的重要性

自己写的类,要有详细的文档。别人的类没有说明,就当从设计者的角度去分析去猜。为了避免猜测,要建立足够详细的说明。


synchronized(myLock){
0 0
原创粉丝点击