本文共 8130 字,大约阅读时间需要 27 分钟。
缓存,大家都知道,有值直接用;无值,计算后放入缓存系统后供下次使用。
定义一个目标接口:Computable
package com.mylearn.cache; /** * Created by IntelliJ IDEA. * User: yingkuohao * Date: 13-11-5 * Time: 下午5:00 * CopyRight:360buy * Descrption: 定义目标接口 * To change this template use File | Settings | File Templates. */ public interface Computable<K, V> { /** * 计算结果 * * @param arg * @return * @throws InterruptedException */ V compute(K arg) throws InterruptedException; } |
CacheUtil0:
package com.mylearn.cache; import java.util.HashMap; import java.util.Map; /** * Created by IntelliJ IDEA. * User: yingkuohao * Date: 13-11-5 * Time: 下午3:05 * CopyRight:360buy * Descrption: * 最原始的缓存系统, * 思想:map中获取value,如果有值,直接使用,如果没值,往map里put value,保存下次能取到。 * 缺点:同步无法保证,保证了同步可能有伸缩行问题,影响性能。 * To change this template use File | Settings | File Templates. */ public class CacheUtil0<K, V> implements Computable<K, V> { Map<K, V> map = new HashMap<K, V>(); private Computable computable; public Computable getComputable() { return computable; } public void setComputable(Computable computable) { this.computable = computable; } /** * 由于使用的是普通的hashMap,线程不安全,所以加上synchronized来同步整个方法, * 这样是可以保证线程安全的,但会带来一个明显的伸缩性问题:每次只有一个线程能够 * 执行compute。如果一个线程正在执行compute方法,另一个线程会阻塞很长时间。 * * @param arg * @return * @throws InterruptedException */ public synchronized V compute(K arg) throws InterruptedException { V value = map.get(arg); if (value == null) { Object newValue = computable.compute(arg); value = (V) newValue; map.put(arg, value); } return value; } } |
package com.mylearn.cache; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * Created by IntelliJ IDEA. * User: yingkuohao * Date: 13-11-5 * Time: 下午3:05 * CopyRight:360buy * Descrption: * 通过读写锁来提高性能,缓存map的核心原理: * 1. map的get操作是不需要加锁的,大家都可以读 * 2. map的put操作是完全互斥的,必须加锁,写的时候不可读,不可其他线程写。 * 所以我们可以根据读写的特性来加一个读写锁提高同步的性能。 * <p/> * 缺点: * 1.我们是否想到了JUC的并发容器:ConcurrentHashMap,通过分段锁是否性能更佳? * 2.即使同步的性能达到了一定高度,但是还是会有计算重复值的问题:多线程同时拿到value为空的情况时都会执行计算, * 这样会导致多个线程重复计算。 * To change this template use File | Settings | File Templates. */ public class CacheUtil1<K, V> implements Computable<K, V> { private final ReadWriteLock lock = new ReentrantReadWriteLock(); //定义读写锁 private final Lock readLock = lock.readLock(); private final Lock writeLock = lock.writeLock(); Map<K, V> map = new HashMap<K, V>(); private Computable computable; public Computable getComputable() { return computable; } public void setComputable(Computable computable) { this.computable = computable; } /** * readLock来控制读:可以随便读,不可写 * * @param arg * @return */ public V get(K arg) { readLock.lock(); try { return map.get(arg); } finally { readLock.unlock(); } } /** * 写锁,写的时候只能自己操作写,其他的读写全都拒绝 * * @param key * @param value * @return */ public V put(K key, V value) { writeLock.lock(); try { return map.put(key, value); } finally { writeLock.unlock(); } } /** * @param arg * @return * @throws InterruptedException */ public V compute(K arg) throws InterruptedException { V value = this.get(arg); if (value == null) { Object newValue = computable.compute(arg); value = (V) newValue; this.put(arg, value); } return value; } } |
package com.mylearn.cache; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * Created by IntelliJ IDEA. * User: yingkuohao * Date: 13-11-5 * Time: 下午3:05 * CopyRight:360buy * Descrption: * ConcurrentHashMap主要采用了分段的技术,即在HashMap的上层增加了数个segment, * 原来的同步相当于锁整个HashMap,现在相当于一个HashMap分成了n个segment,每个segment一个锁, * 这样原来是全锁,现在顶多锁1/n,相当于性能提高了n倍。 * <p/> * 对于get方法,底层采用了volatile变量来标识value和count,所以读的时候直接利用volatile的内存可见性来控制,根本没用锁。 * 对于put方法,先拿到segment的锁,然后进行put操作,这个比我们上例用的WriteLock更好一点,我们相当于锁了整个HashMap,而 * 这里的锁只锁了HashMap的某个segment,其他线程如果操作其他的segment下的元素也是可以的。拥有更好的伸缩性。 * To change this template use File | Settings | File Templates. */ public class CacheUtil2<K, V> implements Computable<K, V> { ConcurrentHashMap<K, V> map = new ConcurrentHashMap<K, V>(); private Computable computable; public Computable getComputable() { return computable; } public void setComputable(Computable computable) { this.computable = computable; } /** * ConcurrentHashMap是线程安全的,用JUC集合类来代替synchronized的同步整个方法行为。 * 避免了在对CacheUtil1中的compute方法进行同步时带来的串行性。 * 缺点:多线程并发执行compute的时候仍然会有计算多次的可能。 * 当某个线程启动了一个很大开销的计算。而其他线程不知道这个计算正在进行,那么和可能会重复计算。 * 我们希望通过某种方法来表达 “线程X正在计算f(27)”这种情况,这样当另一个线程查找f(27)时,它 * 能够知道最高效的方法时等待线程X计算结束,然后再去查询缓存“f(27)的结果是多少”。 * <p/> * 我们已经知道有一个类能基本实现这个功能:FutureTask。FutureTask表示一个计算的过程,这个过程 * 可能已经计算完成,也可能正在进行。如果有结果可用,那么get方法将会立即返回,否则一直阻塞,直到结果 * 计算出来再将其返回。 * * @param arg * @return * @throws InterruptedException */ public V compute(K arg) throws InterruptedException { V value = map.get(arg); if (value == null) { Object newValue = computable.compute(arg); value = (V) newValue; map.put(arg, value); } return value; } } |
package com.mylearn.cache; import java.util.Map; import java.util.concurrent.*; /** * Created by IntelliJ IDEA. * User: yingkuohao * Date: 13-11-5 * Time: 下午3:05 * CopyRight:360buy * Descrption: * To change this template use File | Settings | File Templates. */ public class CacheUtil3<K, V> implements Computable<K, V> { private final ConcurrentHashMap<K, Future<V>> map = new ConcurrentHashMap<K, Future<V>>(); //value变为future private final Computable computable; public Computable getComputable() { return computable; } public CacheUtil3(Computable computable) { this.computable = computable; } /** * 优势:表现出了很好的并发性,若结果已经计算出来,那么立即返回;如果其他线程正在计算,则新线程将一直等待。 * 缺点:仍然会发生两个线程计算出相同值的漏洞。由于 if (future == null) 仍然是非原子的“先检查再执行”操作, * 因此两个线程仍有可能在同一时间内调用compute来计算相同的值。即两者都没有在缓存中找到期望的值,因此都开始计算。 * * 解决方法: map.putIfAbsent(arg, futureTask); * * @param arg * @return * @throws InterruptedException */ public V compute(final K arg) throws InterruptedException { Future<V> future = map.get(arg); if (future == null) { //如果future为空,说明缓存没值,这时new一个线程去计算 Callable<V> callable = new Callable<V>() { public V call() throws InterruptedException { Object newValue = computable.compute(arg); return (V) newValue; } }; FutureTask<V> futureTask = new FutureTask<V>(callable); //我们声明了一个FutureTask future = futureTask; //把future赋值,防止其他线程进入。 if (future != null) 这步 map.put(arg, futureTask); //加入缓存 ,以保证 Future<V> future = map.get(arg);取到值 futureTask.run(); //执行线程,即执行call方法去调用compute } //如果有正在计算的线程,等待其结果 try { return future.get(); //如果线程计算结束,立即返回,否则一直阻塞,直至线程计算完毕。 } catch (ExecutionException e) { throw new InterruptedException(e.getMessage()); } } } |
package com.mylearn.cache; import java.util.concurrent.*; /** * Created by IntelliJ IDEA. * User: yingkuohao * Date: 13-11-5 * Time: 下午3:05 * CopyRight:360buy * Descrption: * To change this template use File | Settings | File Templates. */ public class CacheUtil4<K, V> implements Computable<K, V> { private final ConcurrentHashMap<K, Future<V>> map = new ConcurrentHashMap<K, Future<V>>(); //value变为future private final Computable computable; public Computable getComputable() { return computable; } public CacheUtil4(Computable computable) { this.computable = computable; } /** * * 通过 map.putIfAbsent 来解决两个线程同时put,同时计算的问题。如果添加成功了,下一个线程就直接返回 * 防止重复计算 * * @param arg * @return * @throws InterruptedException */ public V compute(final K arg) throws InterruptedException { while (true) { Future<V> future = map.get(arg); if (future == null) { //如果future为空,说明缓存没值,这时new一个线程去计算 Callable<V> callable = new Callable<V>() { public V call() throws InterruptedException { Object newValue = computable.compute(arg); return (V) newValue; } }; FutureTask<V> futureTask = new FutureTask<V>(callable); //我们声明了一个FutureTask future = map.putIfAbsent(arg, futureTask); //加入缓存 ,没有时才put if (future == null) { //再次判断,如果其他线程已经put成功了,就直接跳出了,等待future.get方法 future = futureTask; //把future赋值,防止其他线程进入。 if (future != null) 这步 futureTask.run(); //执行线程,即执行call方法去调用compute } } //如果有正在计算的线程,等待其结果 try { return future.get(); //如果线程计算结束,立即返回,否则一直阻塞,直至线程计算完毕。 } catch (ExecutionException e) { throw new InterruptedException(e.getMessage()); } } } } |
转载地址:http://ywrrb.baihongyu.com/