首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

Java 容器源码分析之Map-Set-List(20)

Java 容器源码分析之Map-Set-List(20)

总结在实际的应用中,散列表一般的应用场景是:除了少数插入操作和删除操作外,绝大多数都是读取操作,而且读操作在大多数时候都是成功的。正是基于这个前提,ConcurrentHashMap 针对读操作做了大量的优化。通过 HashEntry 对象的不变性和用 volatile 型变量协调线程间的内存可见性,使得 大多数时候,读操作不需要加锁就可以正确获得值。这个特性使得 ConcurrentHashMap 的并发性能在分离锁的基础上又有了近一步的提高。
ConcurrentHashMap 是一个并发散列映射表的实现,它允许完全并发的读取,并且支持给定数量的并发更新。相比于 HashTable 和用同步包装器包装的 HashMap(Collections.synchronizedMap(new HashMap())),ConcurrentHashMap 拥有更高的并发性。在 HashTable 和由同步包装器包装的 HashMap 中,使用一个全局的锁来同步不同线程间的并发访问。同一时间点,只能有一个线程持有锁,也就是说在同一时间点,只能有一个线程能访问容器。这虽然保证多线程间的安全并发访问,但同时也导致对容器的访问变成串行化的了。
ConcurrentHashMap 的高并发性主要来自于三个方面:
  • 用分离锁实现多个线程间的更深层次的共享访问。
  • 用 HashEntery 对象的不变性来降低执行读操作的线程在遍历链表期间对加锁的需求。
  • 通过对同一个 Volatile 变量的写 / 读访问,协调不同线程间读 / 写操作的内存可见性。
使用分离锁,减小了请求 同一个锁的频率。
通过 HashEntery 对象的不变性及对同一个 Volatile 变量的读 / 写来协调内存可见性,使得 读操作大多数时候不需要加锁就能成功获取到需要的值。由于散列映射表在实际应用中大多数操作都是成功的 读操作,所以 2 和 3 既可以减少请求同一个锁的频率,也可以有效减少持有锁的时间。通过减小请求同一个锁的频率和尽量减少持有锁的时间 ,使得 ConcurrentHashMap 的并发性相对于 HashTable 和用同步包装器包装的 HashMap有了质的提高。
LinkedHashMap 与 LRUcacheLRU 缓存介绍我们平时总会有一个电话本记录所有朋友的电话,但是,如果有朋友经常联系,那些朋友的电话号码不用翻电话本我们也能记住,但是,如果长时间没有联系了,要再次联系那位朋友的时候,我们又不得不求助电话本,但是,通过电话本查找还是很费时间的。但是,我们大脑能够记住的东西是一定的,我们只能记住自己最熟悉的,而长时间不熟悉的自然就忘记了。
其实,计算机也用到了同样的一个概念,我们用缓存来存放以前读取的数据,而不是直接丢掉,这样,再次读取的时候,可以直接在缓存里面取,而不用再重新查找一遍,这样系统的反应能力会有很大提高。但是,当我们读取的个数特别大的时候,我们不可能把所有已经读取的数据都放在缓存里,毕竟内存大小是一定的,我们一般把最近常读取的放在缓存里(相当于我们把最近联系的朋友的姓名和电话放在大脑里一样)。
LRU 缓存利用了这样的一种思想。LRU 是 Least Recently Used 的缩写,翻译过来就是“最近最少使用”,也就是说,LRU 缓存把最近最少使用的数据移除,让给最新读取的数据。而往往最常读取的,也是读取次数最多的,所以,利用 LRU 缓存,我们能够提高系统的 performance。
实现要实现 LRU 缓存,我们首先要用到一个类 LinkedHashMap。
用这个类有两大好处:一是它本身已经实现了按照访问顺序的存储,也就是说,最近读取的会放在最前面,最最不常读取的会放在最后(当然,它也可以实现按照插入顺序存储)。第二,LinkedHashMap 本身有一个方法用于判断是否需要移除最不常读取的数,但是,原始方法默认不需要移除(这是,LinkedHashMap 相当于一个linkedlist),所以,我们需要 override 这样一个方法,使得当缓存里存放的数据个数超过规定个数后,就把最不常用的移除掉。关于 LinkedHashMap 中已经有详细的介绍。
代码如下:(可直接复制,也可以通过LRUcache-Java下载)
[url=][/url]
import java.util.LinkedHashMap;import java.util.Collection;import java.util.Map;import java.util.ArrayList;/** * An LRU cache, based on <code>LinkedHashMap</code>. * * <p> * This cache has a fixed maximum number of elements (<code>cacheSize</code>). * If the cache is full and another entry is added, the LRU (least recently * used) entry is dropped. * * <p> * This class is thread-safe. All methods of this class are synchronized. * * <p> * Author: Christian d'Heureuse, Inventec Informatik AG, Zurich, Switzerland<br> * Multi-licensed: EPL / LGPL / GPL / AL / BSD. */public class LRUCache<K, V> {    private static final float hashTableLoadFactor = 0.75f;    private LinkedHashMap<K, V> map;    private int cacheSize;    /**     * Creates a new LRU cache. 在该方法中,new LinkedHashMap<K,V>(hashTableCapacity,     * hashTableLoadFactor, true)中,true代表使用访问顺序     *     * @param cacheSize     *            the maximum number of entries that will be kept in this cache.     */    public LRUCache(int cacheSize) {        this.cacheSize = cacheSize;        int hashTableCapacity = (int) Math                .ceil(cacheSize / hashTableLoadFactor) + 1;        map = new LinkedHashMap<K, V>(hashTableCapacity, hashTableLoadFactor,                true) {            // (an anonymous inner class)            private static final long serialVersionUID = 1;            @Override            protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {                return size() > LRUCache.this.cacheSize;            }        };    }    /**     * Retrieves an entry from the cache.<br>     * The retrieved entry becomes the MRU (most recently used) entry.     *     * @param key     *            the key whose associated value is to be returned.     * @return the value associated to this key, or null if no value with this     *         key exists in the cache.     */    public synchronized V get(K key) {        return map.get(key);    }    /**     * Adds an entry to this cache. The new entry becomes the MRU (most recently     * used) entry. If an entry with the specified key already exists in the     * cache, it is replaced by the new entry. If the cache is full, the LRU     * (least recently used) entry is removed from the cache.     *     * @param key     *            the key with which the specified value is to be associated.     * @param value     *            a value to be associated with the specified key.     */    public synchronized void put(K key, V value) {        map.put(key, value);    }    /**     * Clears the cache.     */    public synchronized void clear() {        map.clear();    }    /**     * Returns the number of used entries in the cache.     *     * @return the number of entries currently in the cache.     */    public synchronized int usedEntries() {        return map.size();    }    /**     * Returns a <code>Collection</code> that contains a copy of all cache     * entries.     *     * @return a <code>Collection</code> with a copy of the cache content.     */    public synchronized Collection<Map.Entry<K, V>> getAll() {        return new ArrayList<Map.Entry<K, V>>(map.entrySet());    }            System.out.println(e.getKey() + " : " + e.getValue());    }}
返回列表