委托是创建线程安全模块最有效的策略。
注意几个要点:
1. 利用委托来确保线程安全----新建一个类,调用非线程安全类的接口,在新建类中完善同步
2. 注意同步时加锁的对象。不可弄错了同步对象
3. 利用clone,将对象复制出来后,修改这个副本,修改完成后,重定向。(这可能伴随着开销问题)
在clone过程中也要加锁。
4. 要注意多线程操作时,不能存在持有锁过长的行为。这将导致吞吐量严重降低
5. 利用ConcurrentModificationException。抛出异常
6. 并发的实现是一个增强一部分功能,削弱一部分功能的过程。有得有失。
7. 在多线程环境下,通过并发容器来代替同步容器,可以提高吞吐量。并发是一种权衡!
HashMap与ConcurrentHashMap
两者的区别是一个是线程安全,一个是线程非安全。
ConcurrentHashMap的加锁机制:分段所。粒度更小。让一定数量的线程可以并发的进行读写。
这种并发,加强了读写,削弱了统计。比如size,isEmpty。
CopyOnWriteArrayList
这个类是在进行操作的时候先复制一个副本出来,然后利用这个副本操作,操作完成后,重定向.
特点:在操作时候(包括add remove set),会调用System.arraycopy,这个导致每次操作的都是不同的引用.避免了java.util.ConcurrentModificationException.同时也付出了性能的代价,这个类不适合经常进行重复的操作.性能消耗太大...
换言之,这个类除了是线程安全的.其他的也没觉得吞吐量有大的改变.反而消耗了性能.
生产者消费者模式
队列有两种,有界队列和无界队列.
无界队列:put操作无阻塞.但是消耗可能会一直增加.导致资源过度负荷
有界队列:有阻塞,但能防止资源过度负荷.!!!即便是超载了.也可以利用offer方法(若数据无法添加则返回失败状态), 通过将工作序列化的手段,写入磁盘,待后续再操作!!!
这里要特别注意有界队列的使用, 它能抑制产生过多的工作项, 使程序在过载的情况下更加强健.
现成类的利用
BlockingQueue支持任意数量的生产者和消费者.
BlockingQueue的多种实现
LinkedBlockingQueue: FIFO
ArrayBlockingQueue: FIFO
PriorityBlockingQueue: 优先级排序
SynchronousQueue: 快速处理模型,没有中间存储.
特点:1. 不允许null元素
2. 阻塞队列,每个put都需要等待一个get
3. 队列容量为0, size方法只有恰巧有放入的时候去读了一下,才能读取到一个1 否则都是0.
4. 线程安全
5. FIFO
对象池
对象池使用的基本思路是:将用过的对象保存起来,等下一次需要这种对象的时候,再拿出来重复使用,从而在一定程度上减少频繁创建对象所造成的开销。 并非所有对象都适合拿来池化――因为维护对象池也要造成一定开销。对生成时开销不大的对象进行池化,反而可能会出现“维护对象池的开销”大于“生成新对象的开销”,从而使性能降低的情况。但是对于生成时开销可观的对象,池化技术就是提高性能的有效策略了。
双端队列
在生产者消费者模式中,每个消费者都有一个双端队列.如果消费者完成了自己的任务,就可以从其他线程中去取.因为是双端队列,所以也不会影响到其他线程.
意义在于让线程尽可能的全部工作,增加效率.
同步工具类
1. 闭锁:一次性,结束后无法重置.适用: 确保某个服务在其他服务启动后在启动, 确保否个操作在其他资源初始化后在操作, 等待所有的参与者都到期了再操作.
实现类:CountDownLatch.java
2. FutureTask:采用Executor框架,异步操作.提前计算某些 高开销操作.
3. 信号量:semaphore--->可用来实现资源池.
4. 栅栏:cyclicBarrier:用在迭代算法, 将一个重复的计算,分成多个子问题,每个子问题分配一个线程进行操作.
Exchange:意在提高应用程序响应时间,
这部分,因为我也没用到过.就不详细看了.知道就好.关键是要知道有这个东西.到时候去查一下.就知道了
高效可伸缩缓存程序
范例:
public interfaceComputable<A, V> {
V compute(A arg) throws InterruptedException;
}
public class ExpensiveFunction implements Computable<String, BigInteger> {
public BigInteger compute(String arg){
return new BigInteger(arg);
}
}
public class Memoizer1<A, V> implements Computable<A, V> {
@GuardedBy("this")
private final Map<A, V> cache = new HashMap<A, V)();
private final Computable<A,V> c;
public Memoizer1(Computable<A, V> c){
this.c = c;
}
public synchronized V compute(A arg) throws InteruptedException {--------------->多线程环境下,函数加锁,会让并发性减弱
V result = cache.get(arg);
if (result == null) {
result = c.compute(arg);
cache.put(arg.result);
}
return result;
}
}
修改1
public class Memoizer1<A, V> implements Computable<A, V> {
@GuardedBy("this")
private final Map<A, V> cache = new ConcurrentHashMap<A, V)();--->从HashMap改成ConcurrentHashMap,增强并发性
private final Computable<A,V> c;
public Memoizer1(Computable<A, V> c){
this.c = c;
}
public V compute(A arg) throws InteruptedException {--------------->去掉synchronized,提高并发性
V result = cache.get(arg);
if (result == null) {
result = c.compute(arg);---->这里可以被并入,因为result可以被同时判断成null,存在计算相同数值的风险
cache.put(arg.result);
}
return result;
}
}
修改2
public class Memoizer1<A, V> implements Computable<A, V> {
@GuardedBy("this")
private final Map<A, V> cache = new ConcurrentHashMap<A, Future<V>)();--->V改成Future<V>,加入线程是否启动计算的判断,大幅降低重复计算率
private final Computable<A,V> c;
public Memoizer1(Computable<A, V> c){
this.c = c;
}
public V compute(A arg) throws InteruptedException {
Future<V> f = cache.get(args);
if(f == null){
Callable<V> eval = new Callable<V>() {
public V call() throws InteruptedException {
return c.compute(args);
}
}
FutureTask<V> ft = new FutureTask<V> (eval);
f = ft;
cache.put(arg, ft);---------->先保存了状态,大幅降低了重复计算。除非同时又在这里重复了。
ft.run();---->可以开始compute计算
}------->将result = c.compute(arg),改成这么一串代码,
try {
return f.get();
}catch(ExcutionException e) {
throw launderThrowable(e.getCause());
}
}
}
修改3.
public class Memoizer1<A, V> implements Computable<A, V> {
@GuardedBy("this")
private final Map<A, V> cache = new ConcurrentHashMap<A, Future<V>)();
private final Computable<A,V> c;
public Memoizer1(Computable<A, V> c){
this.c = c;
}
public V compute(A arg) throws InteruptedException {
Future<V> f = cache.get(args);
if(f == null){
Callable<V> eval = new Callable<V>() {
public V call() throws InteruptedException {
return c.compute(args);
}
}
FutureTask<V> ft = new FutureTask<V> (eval);
f = cache.putIfAbsent(arg, ft);----------->这里再加入重复计算判断。避免之前加过一次。这次就彻底杜绝了。
if (f == null){
f = ft;
ft.run();
}
}
try {
return f.get();
}catch(ExcutionException e) {
throw launderThrowable(e.getCause());
}
}
}
这个流程很有意思。不断的对状态变量进行判断压缩,提高安全系数。
public class Memoizer1<A, V> implements Computable<A, V> {
@GuardedBy("this")
private final Map<A, V> cache = new HashMap<A, V)();
private final Computable<A,V> c;
public Memoizer1(Computable<A, V> c){
this.c = c;
}
public synchronized V compute(A arg) throws InteruptedException {--------------->多线程环境下,函数加锁,会让并发性减弱
V result = cache.get(arg);
if (result == null) {
result = c.compute(arg);
cache.put(arg.result);
}
return result;
}
}