缓存思想分析

缓存思想分析

1. 基本思想

缓存,大家都知道,有值直接用;无值,计算后放入缓存系统后供下次使用。

定义一个目标接口: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,如果有值,直接使用,如果没值,往mapput 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;

    }

}

2. 增加同步的改进

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. mapget操作是不需要加锁的,大家都可以读

 * 2. mapput操作是完全互斥的,必须加锁,写的时候不可读,不可其他线程写。

 * 所以我们可以根据读写的特性来加一个读写锁提高同步的性能。

 * <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;

    }

}

3. 用ConcurrentHashMap的改进

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分成了nsegment,每个segment一个锁,

 * 这样原来是全锁,现在顶多锁1/n,相当于性能提高了n倍。

 * <p/>

 * 对于get方法,底层采用了volatile变量来标识valuecount,所以读的时候直接利用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计算结束,然后再去查询缓存“f27)的结果是多少”。

     * <p/>

     * 我们已经知道有一个类能基本实现这个功能:FutureTaskFutureTask表示一个计算的过程,这个过程

     * 可能已经计算完成,也可能正在进行。如果有结果可用,那么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;

    }

}

4. 用FutureTask的改进

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());

        }

    }

}

5. 减少重复put的继续改进

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());

            }

        }

    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值