细粒度高并行锁 KeyLock

惑惑 2013-07-20 06:21:25
一个细粒度的锁,在某些场景能比synchronized,ReentrantLock等获得更高的并行度更好的性能,感兴趣的可以看看
具体讲解在:http://blog.csdn.net/icebamboo_moyun/article/details/9391915

KeyLock代码:
public class KeyLock<K> {
// 保存所有锁定的KEY及其信号量
private final ConcurrentMap<K, Semaphore> map = new ConcurrentHashMap<K, Semaphore>();
// 保存每个线程锁定的KEY及其锁定计数
private final ThreadLocal<Map<K, LockInfo>> local = new ThreadLocal<Map<K, LockInfo>>() {
@Override
protected Map<K, LockInfo> initialValue() {
return new HashMap<K, LockInfo>();
}
};

/**
* 锁定key,其他等待此key的线程将进入等待,直到调用{@link #unlock(K)}
* 使用hashcode和equals来判断key是否相同,因此key必须实现{@link #hashCode()}和
* {@link #equals(Object)}方法
*
* @param key
*/
public void lock(K key) {
if (key == null)
return;
LockInfo info = local.get().get(key);
if (info == null) {
Semaphore current = new Semaphore(1);
current.acquireUninterruptibly();
Semaphore previous = map.put(key, current);
if (previous != null)
previous.acquireUninterruptibly();
local.get().put(key, new LockInfo(current));
} else {
info.lockCount++;
}
}

/**
* 释放key,唤醒其他等待此key的线程
* @param key
*/
public void unlock(K key) {
if (key == null)
return;
LockInfo info = local.get().get(key);
if (info != null && --info.lockCount == 0) {
info.current.release();
map.remove(key, info.current);
local.get().remove(key);
}
}

/**
* 锁定多个key
* 建议在调用此方法前先对keys进行排序,使用相同的锁定顺序,防止死锁发生
* @param keys
*/
public void lock(K[] keys) {
if (keys == null)
return;
for (K key : keys) {
lock(key);
}
}

/**
* 释放多个key
* @param keys
*/
public void unlock(K[] keys) {
if (keys == null)
return;
for (K key : keys) {
unlock(key);
}
}

private static class LockInfo {
private final Semaphore current;
private int lockCount;

private LockInfo(Semaphore current) {
this.current = current;
this.lockCount = 1;
}
}
}
...全文
1013 61 打赏 收藏 转发到动态 举报
写回复
用AI写文章
61 条回复
切换为时间正序
请发表友善的回复…
发表回复
惑惑 2013-07-25
  • 打赏
  • 举报
回复
引用 50 楼 lcf 的回复:
[quote=引用 48 楼 icebamboo_moyun 的回复:] [quote=引用 46 楼 icebamboo_moyun 的回复:] [quote=引用 41 楼 lcf 的回复:] 这里还得加上条件,Comparator返回相等的时候.equals也必须返回相等,否则会出问题。总之有点太局限了的感觉
有一个解决方案,对任意KEY的hash值加锁[/quote]

	int hash = key.hashCode();
	lock.lock(hash);
	try {
		//do something	
	} finally {
		lock.unlock(hash);
	}
[/quote] 没看懂。。解决了啥问题?[/quote] key只用实现hashCode方法就行,意思是用int值对key进行映射,然后锁其int值
lcf 2013-07-25
  • 打赏
  • 举报
回复
引用 48 楼 icebamboo_moyun 的回复:
[quote=引用 46 楼 icebamboo_moyun 的回复:] [quote=引用 41 楼 lcf 的回复:] 这里还得加上条件,Comparator返回相等的时候.equals也必须返回相等,否则会出问题。总之有点太局限了的感觉
有一个解决方案,对任意KEY的hash值加锁[/quote]

	int hash = key.hashCode();
	lock.lock(hash);
	try {
		//do something	
	} finally {
		lock.unlock(hash);
	}
[/quote] 没看懂。。解决了啥问题?
惑惑 2013-07-25
  • 打赏
  • 举报
回复
引用 43 楼 anybyb 的回复:
什么多线程呀 高并发呀一直都不太明白,不知道有没什么好的文章简单易懂的!
《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 这本书不错。
惑惑 2013-07-25
  • 打赏
  • 举报
回复
引用 46 楼 icebamboo_moyun 的回复:
[quote=引用 41 楼 lcf 的回复:] 这里还得加上条件,Comparator返回相等的时候.equals也必须返回相等,否则会出问题。总之有点太局限了的感觉
有一个解决方案,对任意KEY的hash值加锁[/quote]

	int hash = key.hashCode();
	lock.lock(hash);
	try {
		//do something	
	} finally {
		lock.unlock(hash);
	}
惑惑 2013-07-25
  • 打赏
  • 举报
回复
引用 41 楼 lcf 的回复:
这里还得加上条件,Comparator返回相等的时候.equals也必须返回相等,否则会出问题。总之有点太局限了的感觉
有一个解决方案,对任意KEY的hash值加锁
lcf 2013-07-25
  • 打赏
  • 举报
回复
不小心按了ctrl+enter。。。
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() {
    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啦
lcf 2013-07-25
  • 打赏
  • 举报
回复
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 } }
lcf 2013-07-25
  • 打赏
  • 举报
回复
测试代码:
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即便被线程拥有也会被回收(看来我之前估计得没错。。)
lcf 2013-07-25
  • 打赏
  • 举报
回复
引用 42 楼 raistlic 的回复:
[quote=引用 38 楼 lcf 的回复:] [quote=引用 36 楼 icebamboo_moyun 的回复:] [quote=引用 33 楼 raistlic 的回复:] [quote=引用 26 楼 icebamboo_moyun 的回复:] [quote=引用 23 楼 raistlic 的回复:] 以前从来没用过信号量,仿照你的代码写了一个用普通 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 实例的开销有多大,也许是可以忽略不计的,那么这两种实现也许效率差不多。
测过开销,KeyLock加锁开销约为ReentrantLock的3倍,所以其比较适用于处理耗时长的情景 你的实现也没有考虑到locks map的释放,因为key的范围是不确定的,在运行期间可能处理成千上万个不同的key,可以考虑考虑java的虚引用,也许有方法解决你代码里locks的释放[/quote] 对GC机制不熟悉,如果一个线程在使用某 Object 的 monitor, 算不算强引用? 如果算的话,也许可以用类似 WeakHashMap 的手段。[/quote] 这个我也不是很清楚,如果能用其实现异步清理的话,再加上是使用对象内部锁,那么其性能肯定会比我的实现方案好很多[/quote] 我看了一下ReentrantLock的源码,没有发现它将自己的引用传递给底层。底层引用的是NonfairSync或FairSync,虽然它们都是ReentrantLock的内部类,但它们是static的,所以也没有依赖于ReentrantLock的实例。我估计就算线程hold住lock,那这个lock本身还是可以被垃圾回收的(假设没有任何强引用)。可以做个试验看看。 [/quote] 大概有点超出了我的能力范围,想了半天没有想出一个试验的方案…… 刚刚google了一下没有找到现成的答案,翻了一下 JLS 7 和 oracle的 JVM specification 也没找到答案……[/quote] 我先说一下实验方案,然后我们一起做。 建一个MyReetrantLock类继承ReentrantLock,并重写finalize方法让它输出一个提示 用WeakReference建立一个锁,然后让一个线程锁住它。 这个线程不停地创建新对象并放入一个ArrayList,迫使JVM回收垃圾 间或调用一下System.gc() 最后释放锁,清空ArrayList,再重复一下之前的步骤,再睡一会儿。 最后看输出
阿诺 2013-07-25
  • 打赏
  • 举报
回复
什么多线程呀 高并发呀一直都不太明白,不知道有没什么好的文章简单易懂的!
raistlic 2013-07-25
  • 打赏
  • 举报
回复
引用 38 楼 lcf 的回复:
[quote=引用 36 楼 icebamboo_moyun 的回复:] [quote=引用 33 楼 raistlic 的回复:] [quote=引用 26 楼 icebamboo_moyun 的回复:] [quote=引用 23 楼 raistlic 的回复:] 以前从来没用过信号量,仿照你的代码写了一个用普通 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 实例的开销有多大,也许是可以忽略不计的,那么这两种实现也许效率差不多。
测过开销,KeyLock加锁开销约为ReentrantLock的3倍,所以其比较适用于处理耗时长的情景 你的实现也没有考虑到locks map的释放,因为key的范围是不确定的,在运行期间可能处理成千上万个不同的key,可以考虑考虑java的虚引用,也许有方法解决你代码里locks的释放[/quote] 对GC机制不熟悉,如果一个线程在使用某 Object 的 monitor, 算不算强引用? 如果算的话,也许可以用类似 WeakHashMap 的手段。[/quote] 这个我也不是很清楚,如果能用其实现异步清理的话,再加上是使用对象内部锁,那么其性能肯定会比我的实现方案好很多[/quote] 我看了一下ReentrantLock的源码,没有发现它将自己的引用传递给底层。底层引用的是NonfairSync或FairSync,虽然它们都是ReentrantLock的内部类,但它们是static的,所以也没有依赖于ReentrantLock的实例。我估计就算线程hold住lock,那这个lock本身还是可以被垃圾回收的(假设没有任何强引用)。可以做个试验看看。 [/quote] 大概有点超出了我的能力范围,想了半天没有想出一个试验的方案…… 刚刚google了一下没有找到现成的答案,翻了一下 JLS 7 和 oracle的 JVM specification 也没找到答案……
lcf 2013-07-25
  • 打赏
  • 举报
回复
这里还得加上条件,Comparator返回相等的时候.equals也必须返回相等,否则会出问题。总之有点太局限了的感觉
朗晴 2013-07-25
  • 打赏
  • 举报
回复
高人辈出啊~
惑惑 2013-07-25
  • 打赏
  • 举报
回复
引用 37 楼 lcf 的回复:
[quote=引用 34 楼 raistlic 的回复:] [quote=引用 31 楼 lcf 的回复:] [quote=引用 28 楼 raistlic 的回复:] 楼主链接中这段示例也是有问题的:

    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没有被加锁保护一样。
他这里lock(K[] keys) 是分别锁了keys里的key,并不是将整个keys作为一个锁对待[/quote] 呃,是我看错了,不好意思,用排序来解决死锁问题,思路太巧了,学习了[/quote] 排序解决死锁问题我持保留意见,这方法太容易出莫名其妙的错误(假设用户没仔细看文档),而且效率并不高,有时甚至是不可能的(如果我的Key不能排序?)[/quote] 效率不高倒是肯定的,排序倒是只用传个Comparator就行,用不着KEY有自然顺序,只要保证所有处理用相同顺序请求就行。好在我真实应用中不需要同时锁多个KEY。。。
lcf 2013-07-25
  • 打赏
  • 举报
回复
引用 36 楼 icebamboo_moyun 的回复:
[quote=引用 33 楼 raistlic 的回复:] [quote=引用 26 楼 icebamboo_moyun 的回复:] [quote=引用 23 楼 raistlic 的回复:] 以前从来没用过信号量,仿照你的代码写了一个用普通 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 实例的开销有多大,也许是可以忽略不计的,那么这两种实现也许效率差不多。
测过开销,KeyLock加锁开销约为ReentrantLock的3倍,所以其比较适用于处理耗时长的情景 你的实现也没有考虑到locks map的释放,因为key的范围是不确定的,在运行期间可能处理成千上万个不同的key,可以考虑考虑java的虚引用,也许有方法解决你代码里locks的释放[/quote] 对GC机制不熟悉,如果一个线程在使用某 Object 的 monitor, 算不算强引用? 如果算的话,也许可以用类似 WeakHashMap 的手段。[/quote] 这个我也不是很清楚,如果能用其实现异步清理的话,再加上是使用对象内部锁,那么其性能肯定会比我的实现方案好很多[/quote] 我看了一下ReentrantLock的源码,没有发现它将自己的引用传递给底层。底层引用的是NonfairSync或FairSync,虽然它们都是ReentrantLock的内部类,但它们是static的,所以也没有依赖于ReentrantLock的实例。我估计就算线程hold住lock,那这个lock本身还是可以被垃圾回收的(假设没有任何强引用)。可以做个试验看看。
lcf 2013-07-25
  • 打赏
  • 举报
回复
引用 34 楼 raistlic 的回复:
[quote=引用 31 楼 lcf 的回复:] [quote=引用 28 楼 raistlic 的回复:] 楼主链接中这段示例也是有问题的:

    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没有被加锁保护一样。
他这里lock(K[] keys) 是分别锁了keys里的key,并不是将整个keys作为一个锁对待[/quote] 呃,是我看错了,不好意思,用排序来解决死锁问题,思路太巧了,学习了[/quote] 排序解决死锁问题我持保留意见,这方法太容易出莫名其妙的错误(假设用户没仔细看文档),而且效率并不高,有时甚至是不可能的(如果我的Key不能排序?)
惑惑 2013-07-25
  • 打赏
  • 举报
回复
引用 33 楼 raistlic 的回复:
[quote=引用 26 楼 icebamboo_moyun 的回复:] [quote=引用 23 楼 raistlic 的回复:] 以前从来没用过信号量,仿照你的代码写了一个用普通 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 实例的开销有多大,也许是可以忽略不计的,那么这两种实现也许效率差不多。
测过开销,KeyLock加锁开销约为ReentrantLock的3倍,所以其比较适用于处理耗时长的情景 你的实现也没有考虑到locks map的释放,因为key的范围是不确定的,在运行期间可能处理成千上万个不同的key,可以考虑考虑java的虚引用,也许有方法解决你代码里locks的释放[/quote] 对GC机制不熟悉,如果一个线程在使用某 Object 的 monitor, 算不算强引用? 如果算的话,也许可以用类似 WeakHashMap 的手段。[/quote] 这个我也不是很清楚,如果能用其实现异步清理的话,再加上是使用对象内部锁,那么其性能肯定会比我的实现方案好很多
raistlic 2013-07-25
  • 打赏
  • 举报
回复
引用 30 楼 icebamboo_moyun 的回复:
[quote=引用 28 楼 raistlic 的回复:] 楼主链接中这段示例也是有问题的:

    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没有被加锁保护一样。
这段代码中调用的是这个方法

    /** 
     * 锁定多个key 
     * 建议在调用此方法前先对keys进行排序,使用相同的锁定顺序,防止死锁发生 
     * @param keys 
     */  
    public void lock(K[] keys) {  
        if (keys == null)  
            return;  
        for (K key : keys) {  
            lock(key);  
        }  
    } 
分别请求两个key的锁,而不是将Integer[]作为请求的锁[/quote] 楼上
raistlic 2013-07-25
  • 打赏
  • 举报
回复
引用 31 楼 lcf 的回复:
[quote=引用 28 楼 raistlic 的回复:] 楼主链接中这段示例也是有问题的:

    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没有被加锁保护一样。
他这里lock(K[] keys) 是分别锁了keys里的key,并不是将整个keys作为一个锁对待[/quote] 呃,是我看错了,不好意思,用排序来解决死锁问题,思路太巧了,学习了
raistlic 2013-07-25
  • 打赏
  • 举报
回复
引用 26 楼 icebamboo_moyun 的回复:
[quote=引用 23 楼 raistlic 的回复:] 以前从来没用过信号量,仿照你的代码写了一个用普通 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 实例的开销有多大,也许是可以忽略不计的,那么这两种实现也许效率差不多。
测过开销,KeyLock加锁开销约为ReentrantLock的3倍,所以其比较适用于处理耗时长的情景 你的实现也没有考虑到locks map的释放,因为key的范围是不确定的,在运行期间可能处理成千上万个不同的key,可以考虑考虑java的虚引用,也许有方法解决你代码里locks的释放[/quote] 对GC机制不熟悉,如果一个线程在使用某 Object 的 monitor, 算不算强引用? 如果算的话,也许可以用类似 WeakHashMap 的手段。
加载更多回复(40)

62,617

社区成员

发帖
与我相关
我的任务
社区描述
Java 2 Standard Edition
社区管理员
  • Java SE
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧