Java HashMap的死循环 以及 LRUCache的正确实现

今天RP爆发,16核服务器load飙到30多,cpu使用情况全部99%以上。

从jstack中分析发现全部线程都堵在map.transfer处,如下:

"pool-10-thread-23" prio=10 tid=0x00007fb190003800 nid=0x6350 runnable [0x00007fb64554b000]
   java.lang.Thread.State: RUNNABLE
        at java.util.LinkedHashMap.transfer(LinkedHashMap.java:253)
        at java.util.HashMap.resize(HashMap.java:564)
        at java.util.HashMap.addEntry(HashMap.java:851)
        at java.util.LinkedHashMap.addEntry(LinkedHashMap.java:427)
        at java.util.HashMap.put(HashMap.java:484)

定位问题:

LinkedHashMap非线程安全(本来是借用linkedHashMap实现LRUCache)

问题分析:

详见:http://coolshell.cn/articles/9606.html

问题解决:

采用google的ConcurrentLinkedHashMap(https://code.google.com/p/concurrentlinkedhashmap/

Features
LRU page replacement policy (currently being upgraded to LIRS).
Equivalent performance to ConcurrentHashMap under load.
Can bound by the size of the values (e.g. Multimap cache).
Can notify a listener when an entry is evicted.

cassandra也在concurrentLinkedHashMap的基础上实现了LRUCache,代码如下(微调):

/**
 * 
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * 
 */

import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;

import com.googlecode.concurrentlinkedhashmap.Weighers;
import com.googlecode.concurrentlinkedhashmap.ConcurrentLinkedHashMap;

public class LRULinkedHashMap<K, V> {
    public static final int                     DEFAULT_CONCURENCY_LEVEL = 64;

    private final ConcurrentLinkedHashMap<K, V> map;
    private final AtomicLong                    requests                 = new AtomicLong(0);
    private final AtomicLong                    hits                     = new AtomicLong(0);
    private final AtomicLong                    lastRequests             = new AtomicLong(0);
    private final AtomicLong                    lastHits                 = new AtomicLong(0);
    private volatile boolean                    capacitySetManually;

    public LRULinkedHashMap(int capacity) {
        this(capacity, DEFAULT_CONCURENCY_LEVEL);
    }

    public LRULinkedHashMap(int capacity, int concurrency) {
        map = new ConcurrentLinkedHashMap.Builder<K, V>().weigher(Weighers.<V> singleton())
            .initialCapacity(capacity).maximumWeightedCapacity(capacity)
            .concurrencyLevel(concurrency).build();
    }

    public void put(K key, V value) {
        map.put(key, value);
    }

    public V get(K key) {
        V v = map.get(key);
        requests.incrementAndGet();
        if (v != null)
            hits.incrementAndGet();
        return v;
    }

    public V getInternal(K key) {
        return map.get(key);
    }

    public void remove(K key) {
        map.remove(key);
    }

    public long getCapacity() {
        return map.capacity();
    }

    public boolean isCapacitySetManually() {
        return capacitySetManually;
    }

    public void updateCapacity(int capacity) {
        map.setCapacity(capacity);
    }

    public void setCapacity(int capacity) {
        updateCapacity(capacity);
        capacitySetManually = true;
    }

    public int getSize() {
        return map.size();
    }

    public long getHits() {
        return hits.get();
    }

    public long getRequests() {
        return requests.get();
    }

    public double getRecentHitRate() {
        long r = requests.get();
        long h = hits.get();
        try {
            return ((double) (h - lastHits.get())) / (r - lastRequests.get());
        } finally {
            lastRequests.set(r);
            lastHits.set(h);
        }
    }

    public void clear() {
        map.clear();
        requests.set(0);
        hits.set(0);
    }

    public Set<K> getKeySet() {
        return map.keySet();
    }
}

  

测试:

public static void main(String[] args) {
        LRULinkedHashMap<Integer, Integer> cache = new LRULinkedHashMap<Integer, Integer>(5);
        Random r = new Random();
        for (int i = 0; i < 10; i++) {
            int k = r.nextInt(10);
            System.out.println("input " + k);
            cache.put(k, k);
            System.out.println(cache.getKeySet().toString());
        }
    }

结果如下:

input 1
[1]
input 0
[0, 1]
input 3
[0, 1, 3]
input 4
[0, 1, 4, 3]
input 2
[0, 2, 1, 4, 3]
input 2
[0, 2, 1, 4, 3]
input 4
[0, 2, 1, 4, 3]
input 8
[0, 8, 2, 4, 3]
input 0
[0, 8, 2, 4, 3]
input 2
[0, 8, 2, 4, 3]

  

原文地址:https://www.cnblogs.com/huangfox/p/3156812.html