面临问题
最近一直在忙于新游戏的逻辑开发,在开发过程中发现很多地方都需要用到锁,比如操作背包,用户钱币,用户状态修改等,在以往的实现上大部开发人员都是奖这些操作归结到各个独立的模块中,然后在相应的功能上做一个synchronized或者lock,当然这种做法不是不可以,但从全局上考虑,这种做法似乎有些不太合理,这就如同直接将事务加在dao的curd上,而实际开发中这种事务基本上起不到任何作用,毕竟脱离了业务逻辑的事务没有任何意义.所以我们也需要将锁定的操作扩展到对应的业务功能上.我们先梳理下我们目前的需求,即需要支持对特定的一个或者多个对象的锁定与解锁,你会如何设计这种锁呢?
设计实现
首先定义一下需要实现的方法,根据不同的需要锁定的对象返回List,我们暂时用ChainLock来封装List,代码如下
1 2 3 4 5 6 7 |
public class LockUtils { public static ChainLock getChainLock(Object... objects) { // todo 根据objects返回ChainLock } } |
有了ChainLock后,我们自然需要定义相应的lock与unlock方法
1 2 3 4 5 6 7 8 9 10 11 |
public class ChainLock { public void lock() { // todo 锁定方法 } public void unlock() { // todo 解锁方法 } } |
由于ChainLock需要封装List,这里我们最容易想到的方式就是在lock与unlock方法中将list遍历,但这种方法不太适合用在这里,而这里更合适的方法应该是用我们经常用的责任链模式,我们可以将这个ChainLock做如下改造
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
public class ChainLock { private final Lock current; private final ChainLock next; public ChainLock(List<? extends Lock> locks) { if (CollectionUtils.isEmpty(locks)) { throw new IllegalArgumentException("locks must not null!"); } this.current = locks.remove(0); if (locks.size() > 0) { this.next = new ChainLock(locks); } else { this.next = null; } } public void lock() { this.current.lock(); if (this.next != null) { this.next.lock(); } } public void unlock() { if (this.next != null) { this.next.unlock(); } this.current.unlock(); } } |
实现了ChainLock后,我们需要实现LockUtils的getChainLock方法,实现如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public static ChainLock getChainLock(Object... objects) { return new ChainLock(getLocks(objects)); } private static List<Lock> getLocks(Object... objects) { if (ArrayUtils.isEmpty(objects)) { return null; } List<Lock> locks = new ArrayList<Lock>(); for (Object object : objects) { if (object == null) { throw new NullPointerException(); } Lock lock = Holder.getHolder(object.getClass()).getLock(object); if (!locks.contains(lock)) { locks.add(lock); } } return locks; } |
其实对于Holder来说很简单,它可以是一个简单的Map,key应该是被锁定的Object,而value是对应的Lock,这里我们可以选择最简单的ConcurrentHashMap来实现,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public class Holder { private final static ConcurrentHashMap<Object, Lock> locks = new ConcurrentHashMap<Object, Lock>(); public static Lock getLock(Object) { Lock lock = locks.get(Object); if (lock != null) { return lock; } locks.putIfAbsent(Object, new ReentrantLock()); return locks.get(Object); } } |
这样一个Holder就实现完毕了,但这里会有一个问题,我们在实际使用中会产生很多object,如果这些对象都不能进行及时的回收那么locks将会非常大,当然我们可以在用户下线后投递一个对象销毁事件,将对象从locks中移除,但这样无疑增加了代码的复杂度,这里我们有没有更简单的方法呢?答案是肯定的.
其实无论我们在哪个场景中使用锁,这个锁往往就使用那么一段时间,而这个时间不会很长,所以我们可以考虑使用另一种Map,即WeakHashMap,从名字中我们就能看出来,这个Map中当某个键不再正常使用时,将自动移除其条目,也就是说key中的object不会因为Map对key的引用而不去回收它,一旦除了Map没有其它对象引用该对象,它极有被gc掉,但这个类在提供给我们方便的同时也给我们制造了些麻烦,这个类并不是线程安全的,所以我们需要改造下,参考如下实现.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
public class Holder { private final static ConcurrentMap<Class<?>, Holder> HOLDERS = new ConcurrentHashMap<Class<?>, Holder>(); private final WeakHashMap<Object, Lock> locks = new WeakHashMap<Object, Lock>(); private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); public static Holder getHolder(Class<?> clazz) { Holder holder = HOLDERS.get(clazz); if (holder != null) { return holder; } HOLDERS.putIfAbsent(clazz, new Holder()); return HOLDERS.get(clazz); } public Lock getLock(Object object) { Lock readLock = lock.readLock(); try { readLock.lock(); Lock result = locks.get(object); if (result != null) { return result; } } finally { readLock.unlock(); } return createLock(object); } private Lock createLock(Object object) { Lock writeLock = lock.writeLock(); try { writeLock.lock(); Lock result = locks.get(object); if (result != null) { return result; } Lock objectLock = new ReentrantLock(); locks.put(object, objectLock); return objectLock; } finally { writeLock.unlock(); } } } |
基本上这个锁工具就实现了,同时这个既是线程安全又满足了对object的弱引用,当然我们还可以做的更细致点,针对现实的使用场景定义需要的对象锁类型,例如在实际的开发中我就只定义了背包锁,用户锁,钱包锁,这样就把不同的操作对象进行了拆分归类,以缩短读写锁定的阻塞时间,从而提高性能,并且使用上也很简单,示例代码如下
1 2 3 4 5 6 7 8 9 10 |
public void test(Object object) { ChainLock chainLock = LockUtils.getChainLock(object); try { chainLock.lock(); // TODO 加锁后操作 } finally { chainLock.unlock(); } } |
不太明白这个锁的意义何在?
开头已经提到游戏开发中很多操作是基于多线程操作的,但往往一个操作会触发多个模块功能,比如像交任务,可能涉及到人物属性,背包,任务列表修改等功能,这样我们在进行访问或者修改时需要对这三个模块一起加锁,等待流程走完再依次释放,保证对资源访问与修改是原子性的~
你是刀剑无双的主程?