編寫線程安全的Java緩存讀寫機制 (原創)


一種習以為常的緩存寫法:

IF value in cached THEN
  return value from cache
ELSE
  compute value
  save value in cache
  return value
END IF 

看上去邏輯無比正確,但實際上會造成2種問題:

1、這種方法是不線程安全的。

2、產生數值寫入重復,造成錯誤的數據。

如下圖,在線程1執行計算數值的過程中,線程2也進入數據檢查,將多次寫入數據,程序非常危險。

 

 

演示錯誤代碼:

    //最容易產生的錯誤寫法,先讀取緩存,讀不到就寫緩存
    public Long getNumber(final long index) {
        if (cache.containsKey(index)) {
            return cache.get(index);
        }
        
        final long value = getNumber(index - 1) + getNumber(index - 2);
        cache.put(index, value);
        return value;
    }

 

1、傳統的解決辦法,使用重入鎖 (getNumberByLock 方法)或者同步鎖(getNumberBySynchroniz 方法)。

代碼

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class NaiveCacheExample {

    private final Map<Long, Long> cache = new HashMap<>();
    private Object o=new Object();
    Lock lock =new ReentrantLock();
    
    public NaiveCacheExample() {
        cache.put(0L, 1L);
        cache.put(1L, 1L);
    }
    
    //最容易產生的錯誤寫法,先讀取緩存,讀不到就寫緩存
    public Long getNumber(final long index) {
        if (cache.containsKey(index)) {
            return cache.get(index);
        }
        
        final long value = getNumber(index - 1) + getNumber(index - 2);
        cache.put(index, value);
        return value;
    }

    //使用折返鎖,使讀寫同步
    public Long getNumberByLock(final long index) {       
        long value =0;
        try {
            lock.lock();           
            if (cache.containsKey(index)) {
                return cache.get(index);
            }
            value = getNumberByLock(index - 1) + getNumberByLock(index - 2);
            cache.put(index, value);
            return value;
        }
        catch (Exception e)
        {}
        finally
        {
            lock.unlock();
        }
         
        return 0l;
    }
    
    //使用同步,使讀寫同步
    public Long getNumberBySynchroniz(final long index) {
        synchronized (o)
        {
        long value =0;
        try {
            if (cache.containsKey(index)) {
                return cache.get(index);
            }
            value = getNumberBySynchroniz(index - 1) + getNumberBySynchroniz(index - 2);
            cache.put(index, value);
            return value;
        }
        catch (Exception e)
        {}
        finally
        {
        
        }
        }
        return 0l;
    }


    public static void main(final String[] args) {
       
        NaiveCacheExample naiveCacheExample =new NaiveCacheExample();
        
        Thread threadA =new Thread(new Runnable()
        {
            @Override
            public void run() {
                System.out.println(naiveCacheExample.getNumberBySynchroniz(1000));                
            }
            
        }
        ,"Thread-A");
        threadA.start();
        
        final Thread threadB = new Thread(new Runnable() {
          public void run() {
              System.out.println(naiveCacheExample.getNumberBySynchroniz(1000));
          }
        }, "Thread-B");
        threadB.start();
        

    }
}

 

 

2、一個更好的緩存算法可以用 Callable 和 Future 。 緩存的值將存儲在一個實例 ConcurrentMap 中 ,ConcurrentMap 是線程安全的。

代碼:

import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

public class GenericCacheExample<K, V> {

    private final ConcurrentMap<K, Future<V>> cache = new ConcurrentHashMap<>();

    private Future<V> createFutureIfAbsent(final K key, final Callable<V> callable) {
        Future<V> future = cache.get(key);
        if (future == null) {
            final FutureTask<V> futureTask = new FutureTask<V>(callable);
            future = cache.putIfAbsent(key, futureTask);
            if (future == null) {
                future = futureTask;
                futureTask.run();
            }
        }
        return future;
    }

    public V getValue(final K key, final Callable<V> callable) throws InterruptedException, ExecutionException {
        try {
            final Future<V> future = createFutureIfAbsent(key, callable);
            return future.get();
        } catch (final InterruptedException e) {
            cache.remove(key);
            throw e;
        } catch (final ExecutionException e) {
            cache.remove(key);
            throw e;
        } catch (final RuntimeException e) {
            cache.remove(key);
            throw e;
        }
    }

    public void setValueIfAbsent(final K key, final V value) {
        createFutureIfAbsent(key, new Callable<V>() {
            @Override
            public V call() throws Exception {
                return value;
            }
        });
    }

}

 

參考博客:

http://www.javacreed.com/how-to-cache-results-to-boost-performance/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM