細粒度鎖:
java中的幾種鎖:synchronized,ReentrantLock,ReentrantReadWriteLock已基本可以滿足編程需求,但其粒度都太大,同一時刻只有一個線程能進入同步塊,這對於某些高並發的場景並不適用。比如銀行客戶a向b轉賬,c向d轉賬,假如這兩個線程並發,代碼其實不需要同步。但是同時有線程3,e向b轉賬,那么對b而言必須加入同步。這時需要考慮鎖的粒度問題,即細粒度鎖。
網上搜尋了一些關於java細粒度鎖的介紹文章,大部分是提供思路,比如樂觀鎖,String.intern()和類ConcurrentHashMap,本人對第三種比較感興趣,為此研究了下ConcurrentHashMap的源碼。基於ConcurrentHashMap設計細粒度大志思路如下:
1 Map locks = new Map(); 2 List lockKeys = new List(); 3 for(int number : 1 - 10000) { 4 Object lockKey = new Object(); 5 lockKeys.add(lockKey); 6 locks.put(lockKey, new Object()); 7 } 8 9 public void doSomeThing(String uid) { 10 Object lockKey = lockKeys.get(uid.hash() % lockKeys.size()); 11 Object lock = locks.get(lockKey); 12 13 synchronized(lock) { 14 // do something 15 } 16 }
具體實現如下:
1 public class LockPool { 2 3 //用戶map 4 private static ConcurrentHashMap<String,Object> userMap=new ConcurrentHashMap<String,Object>(); 5 //用戶金額map 6 private static ConcurrentHashMap<String,Integer> moneyMap=new ConcurrentHashMap<String,Integer>(); 7 8 public static void main(String[] args) { 9 LockPool lockPool=new LockPool(); 10 ExecutorService service = Executors.newCachedThreadPool(); 11 service.execute(lockPool.new Boss("u2")); 12 service.execute(lockPool.new Boss("u1")); 13 service.execute(lockPool.new Boss("u1")); 14 service.execute(lockPool.new Boss("u3")); 15 service.execute(lockPool.new Boss("u2")); 16 service.execute(lockPool.new Boss("u2")); 17 service.execute(lockPool.new Boss("u3")); 18 service.execute(lockPool.new Boss("u2")); 19 service.execute(lockPool.new Boss("u2")); 20 service.execute(lockPool.new Boss("u4")); 21 service.execute(lockPool.new Boss("u2")); 22 23 service.shutdown(); 24 25 26 } 27 28 class Boss implements Runnable{ 29 30 private String userId; 31 32 Boss(String userId){ 33 this.userId=userId; 34 } 35 36 @Override 37 public void run() { 38 addMoney(userId); 39 } 40 41 } 42 43 44 public static void addMoney(String userId){ 45 Object obj=userMap.get(userId); 46 if(obj==null){ 47 obj=new Object(); 48 userMap.put(userId,obj); 49 }
//obj是與具體某個用戶綁定,這里應用了synchronized(obj)的小技巧,而不是同步當前整個對象 50 synchronized (obj) { 51 try { 52 System.out.println("-------sleep4s--------"+userId); 53 Thread.sleep(4000); 54 System.out.println("-------awake----------"+userId); 55 } catch (InterruptedException e) { 56 e.printStackTrace(); 57 } 58 if(moneyMap.get(userId)==null){ 59 moneyMap.put(userId,1); 60 }else{ 61 moneyMap.put(userId, moneyMap.get(userId)+1); 62 } 63 System.out.println(userId+"-------moneny----------"+moneyMap.get(userId)); 64 } 65 } 66 67 }
測試結果:
-------sleep4s--------u2
-------sleep4s--------u1
-------sleep4s--------u3
-------sleep4s--------u4
-------awake----------u2
-------awake----------u3
-------awake----------u1
u2-------moneny----------1
u1-------moneny----------1
-------sleep4s--------u1
u3-------moneny----------1
-------sleep4s--------u2
-------sleep4s--------u3
-------awake----------u4
u4-------moneny----------1
-------awake----------u1
u1-------moneny----------2
-------awake----------u3
u3-------moneny----------2
-------awake----------u2
u2-------moneny----------2
-------sleep4s--------u2
-------awake----------u2
u2-------moneny----------3
-------sleep4s--------u2
-------awake----------u2
u2-------moneny----------4
-------sleep4s--------u2
-------awake----------u2
u2-------moneny----------5
-------sleep4s--------u2
-------awake----------u2
u2-------moneny----------6
測試結果來看,只有相同userId的線程才會互斥,同步等待;不同userId的線程沒有同步