先来回顾关于lock的一些概念。

synchronized与java.util.concurrent.locks.Lock 的相同点:Lock能完成synchronized所实现的所有功能;主要不同点:Lock有比synchronized更精确的线程语义和更好的性能。synchronized会自动释放锁,而Lock一定要求程序员手工释放, 并且必须在finally从句中释放。 ReadWriteLock描述的是:一个资源能够被多个读线程访问,或者被一个写线程访问,但是不能同时存在读写线程。也就是说读写锁使用的场合是一个共享资源被大量读取操作,而只有少量的写操作(修改数据)。

public interface ReadWriteLock {
    Lock readLock();
    Lock writeLock();
}

再来看下面这个面试题:

在Java中Lock接口比synchronized块的优势是什么?你需要实现一个高效的缓存,它允许多个用户读,但只允许一个用户写,以此来保持它的完整性,你会怎样去实现它? ReadWriteLock不是Lock的子接口,从源码中可以看出它是借助lock来实现的。 参照jdk文档我用ReadWriteLock实现一个简单的cache。

/*
 * java doc
 * http://download.java.net/jdk7/archive/b123/docs/api/java/util/concurrent/locks/ReentrantReadWriteLock.html
 */
package cahce;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author haleywang.com
 */
public class CacheProvider {

    public static interface CacheConfig {
        long ONE_SECOND = 1000;
        long EXPIRE_TIME = 10 * ONE_SECOND;
    }

    public static class CacheItem {
        private Object value;
        private long createdAt;

        public CacheItem(Object value) {
            super();
            this.value = value;
            this.createdAt = new Date().getTime();
        }

        public boolean expire() {
            return (createdAt + CacheConfig.EXPIRE_TIME) < (new Date()
                    .getTime());
        }

        public Object getValue() {
            return value;
        }

        @Override
        public String toString() {
            return super.toString() + ":" + this.getValue().toString();
        }
    }

    private static volatile CacheProvider instance;

    private static CacheProvider getInstance() {
        if (instance == null) {
            synchronized (CacheProvider.class) {
                if (instance == null) {
                    instance = new CacheProvider();
                }
            }
        }
        return instance;
    }

    private CacheProvider() {
    }

    private final Map<String, CacheItem> cache = new HashMap<String, CacheItem>();
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private final Lock rl = rwl.readLock();
    private final Lock wl = rwl.writeLock();

    public CacheItem getFromCacheAndDb(String key, Callable<Object> callable)
            throws Exception {
        rl.lock();
        try {
            // @1
            if (cache.get(key) == null || cache.get(key).expire()) {
                rl.unlock();
                wl.lock();
                try {
                    if (cache.get(key) == null || cache.get(key).expire()) {
                        if (cache.get(key) != null) {
                            System.out.println("过期了吗(true/false):"
                                    + cache.get(key).expire());
                        }
                        // search db demo
                        cache.put(key, new CacheItem(callable.call()));
                    }
                    rl.lock();
                } finally {
                    wl.unlock();
                }
            }
            // @2

            return cache.get(key);
        } finally {
            rl.unlock();
        }
    }

    @Deprecated
    public CacheItem getFromCacheAndDb0(String key, Callable<Object> callable)
            throws Exception {
        if (cache.get(key) == null) {
            cache.put(key, new CacheItem(callable.call()));
        }
        return cache.get(key);
    }

    public CacheItem get(String key) {
        rl.lock();
        try {
            return cache.get(key);
        } finally {
            rl.unlock();
        }
    }

    public CacheItem put(String key, CacheItem value) {
        wl.lock();
        try {
            return cache.put(key, value);
        } finally {
            wl.unlock();
        }
    }

    public void clear() {
        wl.lock();
        try {
            cache.clear();
        } finally {
            wl.unlock();
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i < 5; i++) {
            testInThread();
        }

        int s = 12;
        try {
            Thread.sleep(s * 1000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(s + "s再来。。。");

        for (int i = 0; i < 5; i++) {
            testInThread();
        }
    }

    static void testInThread() {
        (new Thread() {
            @Override
            public void run() {
                super.run();
                // @3
                try {
                    final String key = "10001";
                    Object obj = CacheProvider.getInstance().getFromCacheAndDb(
                            key, new Callable<Object>() {
                                @Override
                                public Object call() throws Exception {
                                    return UserDao.getDataById(key);
                                }
                            });
                    System.out.println(Thread.currentThread().getName() + ", "
                            + obj);
                } catch (Exception e) {
                    System.err.println(e);
                }

            }
        }).start();
    }
}

/*
 * 简单模拟访问数据库
 */
class UserDao {

    private static Map<String, Object> map = new HashMap<String, Object>();
    static {
        map.put("10001", "jim");
        map.put("10002", "sean");
        map.put("10003", "harry");
        map.put("10004", "haley");
    }

    public static Object getDataById(String name) {
        System.out.println(Thread.currentThread().getName() + ", 访问数据库 ");
        try {
            Thread.sleep(800L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return map.get(name);
    }
}

上面代码中 将testInThread中得getFromCacheAndDb换成getFromCacheAndDb0方法就可以测试不同步的会发生什么情况。 运行输出:

Thread-1, 访问数据库 
Thread-1, cahce.CacheProvider$CacheItem@32f22097:jim
Thread-4, cahce.CacheProvider$CacheItem@32f22097:jim
Thread-2, cahce.CacheProvider$CacheItem@32f22097:jim
Thread-0, cahce.CacheProvider$CacheItem@32f22097:jim
Thread-3, cahce.CacheProvider$CacheItem@32f22097:jim
12s再来。。。
过期了吗(true/false):true
Thread-5, 访问数据库 
Thread-5, cahce.CacheProvider$CacheItem@130a7be0:jim
Thread-6, cahce.CacheProvider$CacheItem@130a7be0:jim
Thread-7, cahce.CacheProvider$CacheItem@130a7be0:jim
Thread-8, cahce.CacheProvider$CacheItem@130a7be0:jim
Thread-9, cahce.CacheProvider$CacheItem@130a7be0:jim

如果testInThread中使用getFromCacheAndDb0方法,就会输出下面结果。

Thread-3, 访问数据库 
Thread-4, 访问数据库 
Thread-0, 访问数据库 
Thread-2, 访问数据库 
Thread-1, 访问数据库 
Thread-2, cahce.CacheProvider$CacheItem@40671416:jim
Thread-0, cahce.CacheProvider$CacheItem@7000bcbc:jim
Thread-4, cahce.CacheProvider$CacheItem@7000bcbc:jim
Thread-1, cahce.CacheProvider$CacheItem@40671416:jim
Thread-3, cahce.CacheProvider$CacheItem@2e739136:jim
12s再来。。。
Thread-5, cahce.CacheProvider$CacheItem@2e739136:jim
Thread-6, cahce.CacheProvider$CacheItem@2e739136:jim
Thread-7, cahce.CacheProvider$CacheItem@2e739136:jim
Thread-8, cahce.CacheProvider$CacheItem@2e739136:jim
Thread-9, cahce.CacheProvider$CacheItem@2e739136:jim

对比什么结果看出不加锁时,并发会访问多次数据库,而实际并不需要多次访问数据库。 这只是简单的实现cache,cache还有很多东西,比如回收策略等等。 三种内存缓存回收策略:LRU、LFU、FIFO。 1.LRU:最近最少使用的,缓存的元素有一个时间戳,当缓存容量满了,而又需要腾出地方来缓存新的元素的时候,那么现有缓存元素中时间戳离当前时间最远的元素将被清除缓存。 2.LFU:最少被使用,缓存的元素有一个hit属性,hit值最小的将会被清除缓存。 3.FIFO:先进先出。


最后来看一个使用ConcurrentHashMap和Future来实现的cache。

package cahce;

import java.util.HashMap;
import java.util.Map;
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<K, Future<V>>();

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

    public static void main(String[] args) throws Exception {
        final GenericCacheExample<String, Object> cache = new GenericCacheExample<String, Object>();

        final String key = "10001";

        for (int i = 0; i < 5; i++) {
            (new Thread() {
                @Override
                public void run() {
                    super.run();
                    try {
                        Object obj = cache.getValue(key,
                                new Callable<Object>() {
                                    @Override
                                    public Object call() throws Exception {
                                        return UserDao1.getDataById(key);
                                    }
                                });
                        System.out.println(Thread.currentThread().getName() + ", " + obj);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (ExecutionException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

/*
 * 简单模拟访问数据库
 */
class UserDao1 {
    private static Map<String, Object> map = new HashMap<String, Object>();
    static {
        map.put("10001", "jim");
        map.put("10002", "sean");
        map.put("10003", "harry");
        map.put("10004", "haley");
    }

    public static Object getDataById(String name) {
        System.out.println(Thread.currentThread().getName() + ", 访问数据库 ");
        try {
            Thread.sleep(800L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return map.get(name);
    }
}