本帖最后由 icebamboo_moyun 于 2013-07-20 18:26:34 编辑

解决方案 »

  1.   

    哦,这是自己实现的,不是JAVA自带的
      

  2.   

    看了你博客上做的测试之后,我有一种更快的方法。方法是不用transfer函数,代码如下:
    public class Test {  private final AtomicInteger[] iAccounts;
      public Test(int count, int money) {
        iAccounts = new AtomicInteger[count];
        for (int i = 0; i < count; i++) 
          iAccounts[i] = new AtomicInteger(money);
      }  void transferAtomic(int from, int to, int money) {
        if (iAccounts[from].get() < money)
          return;
        iAccounts[from].getAndAdd(-money);
        try {
          Thread.sleep(2);
        }
        catch (Exception e) {
        }
        iAccounts[to].getAndAdd(money);
      }
    //...以下略
    新建一个TransferRunner,用transferAtomic而不用transfer函数。此方法比用KeyLock快了10倍。测试的时候去掉那句sleep,就得到了代码的纯效率不同点在于:
    1. KeyLock每次针对两个账户进行了lock,而transferAtomic从本质上来说每次只lock一个账户,也就是它将转出和转入当做两个独立的操作进行:这并不影响账面的正确性
    2. KeyLock有很多内存分配(装箱操作、Semaphore、包括lock之前的Integer{key1, key2})、Hash查询,而transferAtomic除了初始化之外没有任何内存分配及Hash查询,因而效率更高事实上在需要使用KeyLock的时候总会有更优的替代方法(至少我想不到非得用KeyLock的场景),因为它的本质就是分别锁住两个对象(并没有同时,除非在lock()函数中再次加入同步机制)。
      

  3.   

    忘记说了,如果带上sleep,更接近于实际应用场景,transferAtomic依然比KeyLock快20%左右,主要原因是前面讲的第一点,transferAtomic同时只锁住一个账户
      

  4.   

    恩,是的,这个的性能确实差了些。这是我做缓存时用到的工具,其场景类似如下//用户信息
    class UserInfo {
    String name;
    String phone;
    }interface UserInfoDao {
    // 获取用户信息
    UserInfo getUserInfo(int userId); // 更新用户信息
    void updateUserInfo(int userId, UserInfo userInfo);
    }class UserInfoDaoImpl implements UserInfoDao { @Override
    public UserInfo getUserInfo(int userId) {
    UserInfo info = null;
    // 数据库取数据
    return info;
    } @Override
    public void updateUserInfo(int userId, UserInfo userInfo) {
    // 更新数据库
    }}//带缓存的DAO
    class UserInfoDaoCachedImpl implements UserInfoDao {
    ConcurrentMap<Integer, UserInfo> cache;
    UserInfoDaoImpl innerDao;
    KeyLock lock; @Override
    public UserInfo getUserInfo(int userId) {
    UserInfo info = cache.get(userId); // 先从缓存取
    if (info == null) {
    lock.lock(userId);// 缓存无数据则锁定ID
    try {
    info = innerDao.getUserInfo(userId);// 数据库取
    if (info != null) {
    cache.put(userId, info); // 更新缓存
    }
    } finally {
    lock.unlock(userId);
    }
    }
    return info;
    } @Override
    public void updateUserInfo(int userId, UserInfo userInfo) {
    lock.lock(userId);
    try {
    innerDao.updateUserInfo(userId, userInfo);
    cache.put(userId, userInfo);
    } finally {
    lock.unlock(userId);
    }
    }
    }
      

  5.   

    其实我没看懂这里lock的作用。你是想保证任意时刻缓存与Dao的一致性?
    唯一有问题的时候大概是这样:
    T1 updateUserInfo,T2 getUserInfo,此时T1更新了DB却还没更新缓存的时候,T2获取了缓存,与DB不一致。
    但是解决这个问题,只要在updateUserInfo的时候先更新缓存不就好了?可能是你的代码没贴全,所以找不到非要这样做的理由
      

  6.   


    是为了保证缓存和数据库里的数据一致
    但问题不是你说的那个。
    设想那么个不加锁的场景:
    T1和T2同时更新id为0的用户的信息,T1要更新为infoA,T2要更新为infoB
    T1先完成innerDao.updateUserInfo(0, infoA);
    T2后完成innerDao.updateUserInfo(0, infoB);
    这时数据库里存的是infoB
    然后
    T2先完成cache.put(0, infoB);
    T1后完成cache.put(0, infoA);
    这时缓存里存的是infoA,缓存里存的和数据库里存的数据不一样如果对整个updateUserInfo加锁,就能保证缓存,数据库一致,单同一时刻只能有一个线程在更新,其他都得等着,哪怕他们更新的不是同一个用户而用KeyLock对ID加锁,既可以保证数据一致又能提高并行度:对相同ID操作的线程互斥,对不同ID操作的线程可以并行。
    虽然其加锁开销较大,但比较起数据库操作来说基本可以忽略不计
      

  7.   


    仔细看了下你的例子,你在没有加锁的情况下先判断账户余额再转账是不安全的
    比如账户0有500元,T1和T2都要从账户0中转出300元
    T1和T2同时进入余额判断,两线程都满足500 > 300,都进入到了扣钱转账处理,当两线程都处理完成后其账户0的余额是-100元
      

  8.   


    仔细看了下你的例子,你在没有加锁的情况下先判断账户余额再转账是不安全的
    比如账户0有500元,T1和T2都要从账户0中转出300元
    T1和T2同时进入余额判断,两线程都满足500 > 300,都进入到了扣钱转账处理,当两线程都处理完成后其账户0的余额是-100元
    我的测试代码也忽略了这个,锁应该加在判断之前
      

  9.   

    嘛。。这个的确是错的。不过用一个lock array貌似就行了呢
      

  10.   

    你把array加锁那就是所有线程都互斥了,和锁整个方法差不多了。
    锁array + sleep() 效率必须没有KeyLock高
      

  11.   

    我不是锁array。还是看代码直观,我觉得高性能的KeyLock是这样:
    public class KeyLock<T> {
      private Map<T, Lock> iLocksMap;  private Lock iAllocationLock = new ReentrantLock();  public KeyLock() {
        iLocksMap = new ConcurrentHashMap<>();
      }  public void lock(T key) {
        Lock existingLock = iLocksMap.get(key);
        if (existingLock != null) {
          existingLock.lock();
          return;
        }    try {
          iAllocationLock.lock();
          Lock newLock = iLocksMap.get(key);
          if (newLock == null) {
            newLock = new ReentrantLock();
            iLocksMap.put(key, newLock);
          }
          newLock.lock();
          return;
        }
        finally {
          iAllocationLock.unlock();
        }
      }  public void unlock(T key) {
        Lock existingLock = iLocksMap.get(key);
        if (existingLock != null) {
          existingLock.unlock();
          return;
        }    try {
          iAllocationLock.lock();
          existingLock = iLocksMap.get(key);
          if (existingLock != null)
            existingLock.unlock();
        }
        finally {
          iAllocationLock.unlock();
        }
      }
    }
      

  12.   

    嗯,是的,你的实现可以满足需求(unlock方法try里应该是清理map吧?)我因为考虑到锁的可重入性和公平性所以代码才会复杂了点
      

  13.   

    这里的try是为了防止有人同时访问iLocksMap.get(key),然后都拿到了null。那么这两条thread必须只有一条能创建Lock,否则就会出现多个Lock在同一个Key上。不直接用try是为了性能上的考虑,不需要每一次lock或unlock都要调用iAllocationLock。其实这个实现也很公平的,也可以重入
      

  14.   


    嗯,仔细看了下你的代码,发现几个小问题,可能你考虑得不是很周全,说实话多线程的东西确实伤脑筋:
    1、iLocksMap没有进行清理,因为key的范围是不限制的,所以map也会无限制增长
    2、并不是公平锁,公平锁是先请求的先得到锁,new ReentrantLock(true)才是公平锁,new ReentrantLock()是非公平的。你用这个当内部锁控制,那必然也是非公平的
    3、某些情况下不同key也互斥,也就是一个线程锁定key1另一线程无法锁定key2。这个情况有点复杂,结合你的KeyLock代码行数来说明:  T1请求锁定keyA,执行到22行,将newLock放入了iLocksMap,但还未执行23行
      这时T2请求锁定相同keyA,执行到13行,获得existingLock锁,然后return
      T1等待在了24行
      T3请求锁定keyB,等待在了18行,因为iAllocationLock被T1获取,T1还等在24行
    这个情况下T3请求的keyB与T1、T2请求的keyA是不可并行的,T3必须等待T1释放、T2获得、T2释放后才能得到18行的iAllocationLock
      

  15.   

    嗯,又发现一种会造成死锁的情况,这个比较严重,表现在同一线程请求两个KEY锁的情况,同样结合代码行说明:
      T1请求锁定keyA,执行到22行,将newLock放入了iLocksMap,但还未执行23行
      这时T2请求锁定相同keyA,执行到13行,获得existingLock锁,然后return
      T1等待在了24行
      T2再次请求锁定keyB,等待在了18行,因为iAllocationLock被T1获取,T1还等在24行
    这个情况下
      T1获得iAllocationLock在等待newLock
      T2获得existingLock在等待iAllocationLock
    由于newLock和existingLock是同一个锁,所以死锁了
      

  16.   


    这段代码明显有问题:
        // ...
        if (iAccounts[from].get() < money)
          return;
        iAccounts[from].getAndAdd(-money);
        // ...
      

  17.   

    以前从来没用过信号量,仿照你的代码写了一个用普通 Object 加锁的类,用了 ConcurrentHashMap 和 double-checked lock 来保证获取锁时重复的读操作 lock free:import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;public class KeyLocks<L> {
      
      private final Map<L, Object> locks = new ConcurrentHashMap<L, Object>();
      
      public Object getLock(L key) {
        
        Object result = locks.get(key);
        if( result == null ) synchronized (locks) {
          
          result = locks.get(key);
          if( result == null ) {
            
            result = new Object();
            locks.put(key, result);
          }
        }
        return result;
      }
    }class Test {
      
      private final int[] accounts;
      private final KeyLocks<Integer> keyLocks;
      
      Test(int count, int money) {
        
        if( count <= 0 )
          throw new IllegalArgumentException("Invalid number of accounts: " + count);
        if( money < 0 )
          throw new IllegalArgumentException("Invalid initial balance: " + money);
        
        accounts = new int[count];
        for(int i=0; i<accounts.length; i++)
          accounts[i] = money;
        
        keyLocks = new KeyLocks<Integer>();
      }
      
      boolean transfer(int from, int to, int money) {
        
        if( from < 0 || from >= accounts.length )
          throw new IndexOutOfBoundsException("Invalid from account: " + from);
        
        if( to < 0 || to >= accounts.length )
          throw new IndexOutOfBoundsException("Invalid to account: " + to);
        
        if( from == to )
          throw new IllegalArgumentException("Cannot transfer between same account.");
        
        if( money < 0 )
          throw new IllegalArgumentException("Invalid transfer amount: " + money);
        
        synchronized (keyLocks.getLock(from)) {
            
            if( accounts[from] < money )
              return false;
            
            accounts[from] -= money;
        }
        synchronized (keyLocks.getLock(to)) {
          
          accounts[to] += money;
        }
        return true;
      }
    }
    我不知道每次访问 ThreadLocal 和 建立 Semaphore 实例的开销有多大,也许是可以忽略不计的,那么这两种实现也许效率差不多。
      

  18.   

    果然是有问题的么。。
    如果把try里面的newlock.lock放到finally之后貌似就行了
      

  19.   


    同意你说的,如果具体到某场景应该总有更优化的实现方式。AtomicInteger 的问题在于,它不能支持 “读取,检查然后决定是否写入” 这种复杂组合的原子性保护。
      

  20.   


    测过开销,KeyLock加锁开销约为ReentrantLock的3倍,所以其比较适用于处理耗时长的情景你的实现也没有考虑到locks map的释放,因为key的范围是不确定的,在运行期间可能处理成千上万个不同的key,可以考虑考虑java的虚引用,也许有方法解决你代码里locks的释放
      

  21.   

    要做map清理,除了用threadlocal来跟踪之外貌似没什么好办法。即便加一个dumpLock()的函数,使用不当还是非常容易出现IllegalMonitorStateException。像这样
    public class KeyLock<T> {
      private final Map<T, Lock> iLocksMap;  private final boolean iFair;  private Lock iAllocationLock;  public KeyLock(boolean fair) {
        iFair = fair;
        iLocksMap = new ConcurrentHashMap<>();
        iAllocationLock = new ReentrantLock(iFair);
      }  public void lock(T key) {
        Lock existingLock = iLocksMap.get(key);
        if (existingLock != null) {
          existingLock.lock();
          return;
        }    Lock newLock = null;
        try {
          iAllocationLock.lock();
          newLock = iLocksMap.get(key);
          if (newLock == null) {
            newLock = new ReentrantLock(iFair);
            iLocksMap.put(key, newLock);
          }
          return;
        }
        finally {
          iAllocationLock.unlock();
          newLock.lock();
        }
      }  public void unlock(T key) {
        Lock existingLock = iLocksMap.get(key);
        if (existingLock != null) {
          existingLock.unlock();
          return;
        }    try {
          iAllocationLock.lock();
          existingLock = iLocksMap.get(key);
          if (existingLock != null)
            existingLock.unlock();
        }
        finally {
          iAllocationLock.unlock();
        }
      }  public void dumpLock(T key) {
        if (iLocksMap.remove(key) != null)
          return;    try {
          iAllocationLock.lock();
          if (iLocksMap.remove(key) != null)
            return;
        }
        finally {
          iAllocationLock.unlock();
        }    iLocksMap.remove(key);
      }
      

  22.   

    楼主链接中这段示例也是有问题的:
        private int[] accounts;  
        private KeyLock<Integer> lock = new KeyLock<Integer>();  
          
        public boolean transfer(int from, int to, int money) {  
            Integer[] keys = new Integer[] {from, to};  
            Arrays.sort(keys); //对多个key进行排序,保证锁定顺序防止死锁  
            lock.lock(keys);  
            try {  
                //处理不同的from和to的线程都可进入此同步块  
                if (accounts[from] < money)  
                    return false;  
                accounts[from] -= money;  
                accounts[to] += money;  
                return true;  
            } finally {  
                lock.unlock(keys);  
            }  
        }  
    (顺便,里面的 KeyLock<Integer> 应该是 KeyLock<Integer[]> 吧)假设帐号 3 有 500
    如果有两个操作同时发生在两个线程里:从 3 到 5 转帐 300
    从 3 到 6 转帐 300由于 {3,5} 和 {3,6} 是不相等的两把锁,会导致两个线程可能同时访问到 帐号3, 就像3没有被加锁保护一样。
      

  23.   

    unlock中iAllocationLock还可以更早一步释放public void unlock(T key) {
        Lock existingLock = iLocksMap.get(key);
        if (existingLock != null) {
          existingLock.unlock();
          return;
        }    try {
          iAllocationLock.lock();
          existingLock = iLocksMap.get(key);
        }
        finally {
          iAllocationLock.unlock();
          if (existingLock != null)
            existingLock.unlock();
        }
      }
      

  24.   


    这段代码中调用的是这个方法    /** 
         * 锁定多个key 
         * 建议在调用此方法前先对keys进行排序,使用相同的锁定顺序,防止死锁发生 
         * @param keys 
         */  
        public void lock(K[] keys) {  
            if (keys == null)  
                return;  
            for (K key : keys) {  
                lock(key);  
            }  
        } 分别请求两个key的锁,而不是将Integer[]作为请求的锁
      

  25.   

    他这里lock(K[] keys) 是分别锁了keys里的key,并不是将整个keys作为一个锁对待
      

  26.   

    恩,如果不考虑iLocksMap的清理问题,是比较容易解决的,加上清理后代码也就会变得复杂了
      

  27.   


    测过开销,KeyLock加锁开销约为ReentrantLock的3倍,所以其比较适用于处理耗时长的情景你的实现也没有考虑到locks map的释放,因为key的范围是不确定的,在运行期间可能处理成千上万个不同的key,可以考虑考虑java的虚引用,也许有方法解决你代码里locks的释放对GC机制不熟悉,如果一个线程在使用某 Object 的 monitor, 算不算强引用? 如果算的话,也许可以用类似 WeakHashMap 的手段。
      

  28.   

    他这里lock(K[] keys) 是分别锁了keys里的key,并不是将整个keys作为一个锁对待呃,是我看错了,不好意思,用排序来解决死锁问题,思路太巧了,学习了
      

  29.   


    这段代码中调用的是这个方法    /** 
         * 锁定多个key 
         * 建议在调用此方法前先对keys进行排序,使用相同的锁定顺序,防止死锁发生 
         * @param keys 
         */  
        public void lock(K[] keys) {  
            if (keys == null)  
                return;  
            for (K key : keys) {  
                lock(key);  
            }  
        } 分别请求两个key的锁,而不是将Integer[]作为请求的锁楼上
      

  30.   


    测过开销,KeyLock加锁开销约为ReentrantLock的3倍,所以其比较适用于处理耗时长的情景你的实现也没有考虑到locks map的释放,因为key的范围是不确定的,在运行期间可能处理成千上万个不同的key,可以考虑考虑java的虚引用,也许有方法解决你代码里locks的释放对GC机制不熟悉,如果一个线程在使用某 Object 的 monitor, 算不算强引用? 如果算的话,也许可以用类似 WeakHashMap 的手段。这个我也不是很清楚,如果能用其实现异步清理的话,再加上是使用对象内部锁,那么其性能肯定会比我的实现方案好很多
      

  31.   

    他这里lock(K[] keys) 是分别锁了keys里的key,并不是将整个keys作为一个锁对待呃,是我看错了,不好意思,用排序来解决死锁问题,思路太巧了,学习了排序解决死锁问题我持保留意见,这方法太容易出莫名其妙的错误(假设用户没仔细看文档),而且效率并不高,有时甚至是不可能的(如果我的Key不能排序?)
      

  32.   


    测过开销,KeyLock加锁开销约为ReentrantLock的3倍,所以其比较适用于处理耗时长的情景你的实现也没有考虑到locks map的释放,因为key的范围是不确定的,在运行期间可能处理成千上万个不同的key,可以考虑考虑java的虚引用,也许有方法解决你代码里locks的释放对GC机制不熟悉,如果一个线程在使用某 Object 的 monitor, 算不算强引用? 如果算的话,也许可以用类似 WeakHashMap 的手段。这个我也不是很清楚,如果能用其实现异步清理的话,再加上是使用对象内部锁,那么其性能肯定会比我的实现方案好很多我看了一下ReentrantLock的源码,没有发现它将自己的引用传递给底层。底层引用的是NonfairSync或FairSync,虽然它们都是ReentrantLock的内部类,但它们是static的,所以也没有依赖于ReentrantLock的实例。我估计就算线程hold住lock,那这个lock本身还是可以被垃圾回收的(假设没有任何强引用)。可以做个试验看看。
      

  33.   

    他这里lock(K[] keys) 是分别锁了keys里的key,并不是将整个keys作为一个锁对待呃,是我看错了,不好意思,用排序来解决死锁问题,思路太巧了,学习了排序解决死锁问题我持保留意见,这方法太容易出莫名其妙的错误(假设用户没仔细看文档),而且效率并不高,有时甚至是不可能的(如果我的Key不能排序?)
    效率不高倒是肯定的,排序倒是只用传个Comparator就行,用不着KEY有自然顺序,只要保证所有处理用相同顺序请求就行。好在我真实应用中不需要同时锁多个KEY
      

  34.   

    这里还得加上条件,Comparator返回相等的时候.equals也必须返回相等,否则会出问题。总之有点太局限了的感觉
      

  35.   

    测过开销,KeyLock加锁开销约为ReentrantLock的3倍,所以其比较适用于处理耗时长的情景你的实现也没有考虑到locks map的释放,因为key的范围是不确定的,在运行期间可能处理成千上万个不同的key,可以考虑考虑java的虚引用,也许有方法解决你代码里locks的释放对GC机制不熟悉,如果一个线程在使用某 Object 的 monitor, 算不算强引用? 如果算的话,也许可以用类似 WeakHashMap 的手段。这个我也不是很清楚,如果能用其实现异步清理的话,再加上是使用对象内部锁,那么其性能肯定会比我的实现方案好很多我看了一下ReentrantLock的源码,没有发现它将自己的引用传递给底层。底层引用的是NonfairSync或FairSync,虽然它们都是ReentrantLock的内部类,但它们是static的,所以也没有依赖于ReentrantLock的实例。我估计就算线程hold住lock,那这个lock本身还是可以被垃圾回收的(假设没有任何强引用)。可以做个试验看看。
    大概有点超出了我的能力范围,想了半天没有想出一个试验的方案……
    刚刚google了一下没有找到现成的答案,翻了一下 JLS 7 和 oracle的 JVM specification 也没找到答案……
      

  36.   


    测过开销,KeyLock加锁开销约为ReentrantLock的3倍,所以其比较适用于处理耗时长的情景你的实现也没有考虑到locks map的释放,因为key的范围是不确定的,在运行期间可能处理成千上万个不同的key,可以考虑考虑java的虚引用,也许有方法解决你代码里locks的释放对GC机制不熟悉,如果一个线程在使用某 Object 的 monitor, 算不算强引用? 如果算的话,也许可以用类似 WeakHashMap 的手段。这个我也不是很清楚,如果能用其实现异步清理的话,再加上是使用对象内部锁,那么其性能肯定会比我的实现方案好很多我看了一下ReentrantLock的源码,没有发现它将自己的引用传递给底层。底层引用的是NonfairSync或FairSync,虽然它们都是ReentrantLock的内部类,但它们是static的,所以也没有依赖于ReentrantLock的实例。我估计就算线程hold住lock,那这个lock本身还是可以被垃圾回收的(假设没有任何强引用)。可以做个试验看看。
    大概有点超出了我的能力范围,想了半天没有想出一个试验的方案……
    刚刚google了一下没有找到现成的答案,翻了一下 JLS 7 和 oracle的 JVM specification 也没找到答案……我先说一下实验方案,然后我们一起做。
    建一个MyReetrantLock类继承ReentrantLock,并重写finalize方法让它输出一个提示
    用WeakReference建立一个锁,然后让一个线程锁住它。
    这个线程不停地创建新对象并放入一个ArrayList,迫使JVM回收垃圾
    间或调用一下System.gc()
    最后释放锁,清空ArrayList,再重复一下之前的步骤,再睡一会儿。
    最后看输出
      

  37.   

    测试代码:
    public class Father {  public static void main(String args[]) {
        new Thread(new Runnable() {
          @Override
          public void run() {
            MyLock strongLock = new MyLock();
            WeakReference<MyLock> weakLock = new WeakReference<MyLock>(strongLock);
            strongLock.lock();
            System.out.println("locked");
            strongLock = null;        System.out.println("Allocation like crazy");
            List<String> garbage = new ArrayList<String>();
            for (int i = 0; i < 1000000; i++) {
              garbage.add(String.valueOf(i));
            }        System.out.println("calling gc");
            System.gc();        try {
              Thread.sleep(1000);
            }
            catch (InterruptedException e) {
              //ignore
            }        MyLock lock = weakLock.get();
            if (lock == null) {
              System.out.println("damn!");
              return;
            }
            else {
              lock.unlock();
            }        garbage.clear();        System.out.println("Allocation like crazy again");
            for (int i = 1000000; i < 2000000; i++) {
              garbage.add(String.valueOf(i));
            }        System.out.println("calling gc");
            System.gc();
            try {
              Thread.sleep(1000);
            }
            catch (InterruptedException e) {
              // ignore
            }        if (weakLock.get() == null)
              System.out.println("hooray!!");
          }
        }).start();
      }  private static class MyLock extends ReentrantLock {
        @Override
        public void finalize() {
          System.out.println("Hi this is me, a lock! But I'm dying, see ya");
        }
      }
    }测试结果:
    locked
    Allocation like crazy
    Hi this is me, a lock! But I'm dying, see ya
    calling gc
    damn!证明Lock即便被线程拥有也会被回收(看来我之前估计得没错。。)
      

  38.   

    有一个解决方案,对任意KEY的hash值加锁
      

  39.   

    有一个解决方案,对任意KEY的hash值加锁 int hash = key.hashCode();
    lock.lock(hash);
    try {
    //do something
    } finally {
    lock.unlock(hash);
    }
      

  40.   

    《Java并发编程实战》http://www.amazon.cn/Java%E5%B9%B6%E5%8F%91%E7%BC%96%E7%A8%8B%E5%AE%9E%E6%88%98-%E7%9B%96%E8%8C%A8/dp/B0077K9XHW/ref=sr_1_1?s=books&ie=UTF8&qid=1374725184&sr=1-1&keywords=java+%E5%B9%B6%E5%8F%91
    这本书不错。
      

  41.   

    有一个解决方案,对任意KEY的hash值加锁 int hash = key.hashCode();
    lock.lock(hash);
    try {
    //do something
    } finally {
    lock.unlock(hash);
    }没看懂。。解决了啥问题?
      

  42.   

    有一个解决方案,对任意KEY的hash值加锁 int hash = key.hashCode();
    lock.lock(hash);
    try {
    //do something
    } finally {
    lock.unlock(hash);
    }没看懂。。解决了啥问题?
    key只用实现hashCode方法就行,意思是用int值对key进行映射,然后锁其int值
      

  43.   

    代码里都没有循环
    不会这样用hash加锁唯一会出现的情况是,两个不同的key它们的hash值一样,两个线程对这两个key同时操作时会互斥,因为KeyLock认的是其hash值
    用hash来锁定还有一个好处,其KeyLock内部的MAP效率更高,因为判断相等是用的Integer,其hashCode和equals方法效率高
      

  44.   


    这样的话,就不能随意释放map中的锁了……
      

  45.   

    代码里都没有循环
    不会这样用hash加锁唯一会出现的情况是,两个不同的key它们的hash值一样,两个线程对这两个key同时操作时会互斥,因为KeyLock认的是其hash值
    用hash来锁定还有一个好处,其KeyLock内部的MAP效率更高,因为判断相等是用的Integer,其hashCode和equals方法效率高o,你的意思是让外部代码来调用hashCode,我以为是在KeyLock内部进行这样的处理。。
    不管是内部还是外部这样做肯定不行的,原因就是你说的那条hash值冲突问题。在这个前提下讨论效率没什么意义了,因为正确性是第一位的。当然如果是你说的那种特殊应用场景,那是没问题,而且甚至应该用trove的TIntegerObjectMap来替代你的ConcurrentMap,效率更高。
      

  46.   

    嗯。。实现了守护线程对map的清理
    public class KeyLock2<T> {
      private final Map<T, Lock> iLocksMap;  private final boolean iFair;  private Lock iAllocationLock;  private Lock iFreeMapLock;  private ScheduledExecutorService iExecutor;  public KeyLock2(boolean fair, long collectionRateMilli) {
        iFair = fair;
        iLocksMap = new ConcurrentHashMap<T, Lock>();
        iAllocationLock = new ReentrantLock(iFair);    iFreeMapLock = new ReentrantLock();
        iExecutor = Executors.newScheduledThreadPool(1);    iExecutor.scheduleWithFixedDelay(new Runnable() {
          @Override
          public void run() {
            dumpLocks();
          }
        }, 0, collectionRateMilli, TimeUnit.MILLISECONDS);
      }  public void lock(T key) {
        Lock newLock = null;
        try {
          iAllocationLock.lock();
          newLock = iLocksMap.get(key);
          if (newLock == null) {
            newLock = new ReentrantLock(iFair);
            iLocksMap.put(key, newLock);
          }
          return;
        }
        finally {
          iAllocationLock.unlock();
          if (newLock != null)
            newLock.lock();
        }
      }  public void unlock(T key) {
        Lock existingLock;
        try {
          iAllocationLock.lock();
          existingLock = iLocksMap.get(key);
        }
        finally {
          iAllocationLock.unlock();
        }    if (existingLock != null)
          existingLock.unlock();
      }  public void dumpLocks() {
        iFreeMapLock.lock();
        try {
          for (Iterator<Map.Entry<T, Lock>> entryIter = iLocksMap.entrySet().iterator(); entryIter.hasNext();/*empty*/) {
            Map.Entry<T, Lock> entry = entryIter.next();
            if (entry.getValue().tryLock()) {
              iAllocationLock.lock();
              try {
                entryIter.remove();
              }
              finally {
                iAllocationLock.unlock();
              }
            }
          }
        }
        finally {
          iFreeMapLock.unlock();
        }
      }
    }
      

  47.   

    那还不如直接用我的方案呢
    每个KeyLock对象就包含一清理线程,程序里可不止只有一个KeyLock对象,有对人员ID加锁的,有对工单ID加锁的,有对
      

  48.   

    但是他们都可以用同一个KeyLock对象啊
      

  49.   

    不可,比如人员有ID为1的,工单也有ID为1的,他们互不干扰,不需互斥
      

  50.   

    class LockKey {
      private int iKey;
      private int iType;
      private LockKey (int key, int type) {
        iKey = key;
        iType = type;
      }  public static getIDLockKey (int key) {
        return new LockKey(key, 0);
      }  public static getBillLockKey (int key) {
        return new LockKey(key, 1);
      }  public int getKey() {
      }  public boolean equals (Object o) {
        if (!o instanceof LockKey)
          return false;
        return (LockKey) o.iKey == iKey
      }
    }
      

  51.   

    不小心按了ctrl+enterclass LockKey {
      private int iKey;
      private int iType;
      private LockKey (int key, int type) {
        iKey = key;
        iType = type;
      }  public static getIDLockKey (int key) {
        return new LockKey(key, 0);
      }  public static getBillLockKey (int key) {
        return new LockKey(key, 1);
      }  public int getKey() {
        return iKey;
      }  public boolean equals (Object o) {
        if (o == null || !o instanceof LockKey)
          return false;
        return ((LockKey) o).iKey == iKey && ((LockKey) o).iType == iType;
      }  public int hashCode() {
        return iKey * 17 + (iKey << iType) * 17;
      }
    }如此就可以公用KeyLock啦