构建高性能服务(二)java高并发锁的3种实现

来源:http://www.xymyeah.com/?p=46
 

提高系统并发吞吐能力是构建高性能服务的重点和难点。通常review代码时看到synchronized是我都会想一想,这个地方可不可以优化。使用synchronized使得并发的线程变成顺序执行,对系统并发吞吐能力有极大影响,我的博文 http://maoyidao.iteye.com/blog/1149015 介绍了可以从理论上估算系统并发处理能力的方法。

那么对于必须使用synchronized的业务场景,这里提供几个小技巧,帮助大家减小锁粒度,提高系统并发能力。

初级技巧 - 乐观锁

乐观锁适合这样的场景:读不会冲突,写会冲突。同时读的频率远大于写。

以下面的代码为例,悲观锁的实现:

  1. public Object get(Object key) {
  2. synchronized(map) {
  3. if(map.get(key) == null) {
  4. // set some values
  5. }
  6. return map.get(key);
  7. }
  8. }

乐观锁的实现:

  1. public Object get(Object key) {
  2. Object val = null;
  3. if((val = map.get(key) == null) {
  4. // 当map取值为null时再加锁判断
  5. synchronized(map) {
  6. if(val = map.get(key) == null) {
  7. // set some value to map...
  8. }
  9. }
  10. }
  11. return map.get(key);
  12. }

中级技巧 - String.intern()

乐观锁不能很好解决大量写冲突问题,但是如果很多场景下,锁实际上只是针对某个用户或者某个订单。比如一个用户必须先创建session,才能进行后面的操作。但是由于网络原因,创建用户session的请求和后续请求几乎同时达到,而并行线程可能会先处理后续请求。一般情况,需要对用户sessionMap加锁,比如上面的乐观锁。在这种场景下,可以讲锁限定到用户本身上,即从原来的

lock.lock();

int num=storage.get(key);

storage.set(key,num+1);

lock.unlock();

更改为:

lock.lock(key);

int num=storage.get(key);

storage.set(key,num+1);

lock.unlock(key);

这个比较类似于数据库表锁和行锁的概念,显然行锁的并发能力比表锁高很多。

使用String.inter()是这种思路的一种具体实现。类 String 维护一个字符串池。 当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。可见,当String相同时,String.intern()总是返回同一个对象,因此就实现了对同一用户加锁。由于锁的粒度局限于具体用户,使系统获得了最大程度的并发。

  1. public void doSomeThing(String uid) {
  2. synchronized(uid.intern()) {
  3. // ...
  4. }
  5. }

CopyOnWriteMap?

既然说到了“类似于数据库中的行锁的概念”,就不得不提一下MVCC,Java中CopyOnWrite类实现了MVCC。Copy On Write是这样一种机制。当我们读取共享数据的时候,直接读取,不需要同步。当我们修改数据的时候,我们就把当前数据Copy一份副本,然后在这个副本 上进行修改,完成之后,再用修改后的副本,替换掉原来的数据。这种方法就叫做Copy On Write。

但是,,,JDK并没有提供CopyOnWriteMap,为什么?下面有个很好的回答,那就是已经有了ConcurrentHashMap,为什么还需要CopyOnWriteMap?

Fredrik Bromee 写道
I guess this depends on your use case, but why would you need a CopyOnWriteMap when you already have a ConcurrentHashMap?

For a plain lookup table with many readers and only one or few updates it is a good fit.

Compared to a copy on write collection:

Read concurrency:

Equal to a copy on write collection. Several readers can retrieve elements from the map concurrently in a lock-free fashion.

Write concurrency:

Better concurrency than the copy on write collections that basically serialize updates (one update at a time). Using a concurrent hash map you have a good chance of doing several updates concurrently. If your hash keys are evenly distributed.

If you do want to have the effect of a copy on write map, you can always initialize a ConcurrentHashMap with a concurrency level of 1.

高级技巧 - 类ConcurrentHashMap

String.inter()的缺陷是类 String 维护一个字符串池是放在JVM perm区的,如果用户数特别多,导致放入字符串池的String不可控,有可能导致OOM错误或者过多的Full GC。怎么样能控制锁的个数,同时减小粒度锁呢?直接使用Java ConcurrentHashMap?或者你想加入自己更精细的控制?那么可以借鉴ConcurrentHashMap的方式,将需要加锁的对象分为多个bucket,每个bucket加一个锁,伪代码如下:

  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. public void doSomeThing(String uid) {
  9. Object lockKey = lockKeys.get(uid.hash() % lockKeys.size());
  10. Object lock = locks.get(lockKey);
  11. synchronized(lock) {
  12. // do something
  13. }
  14. }

关于高性能缓存的设计,请参考构建高性能服务系列之一:http://www.xymyeah.com/?p=10

更多http://www.xymyeah.com/?p=46

最新文章

  1. SDOI 2016 游戏
  2. activity 、window与view的关系 (上)
  3. 正确运用synchronized和二次判断 实现多线程安全
  4. macosx安装MySQLdb
  5. VMware网络选项分析
  6. Spring有什么缺点?
  7. element-ui对话框组件Dialog在回调事件opened获取组件滚动条scrollTop的问题
  8. Python函数式编程之闭包
  9. memge和saveOrUpdate的区别
  10. ActiveMq报错Channel was inactive for too (>30000)long
  11. POJ 3621-Sightseeing Cows-最优比率环|SPFA+二分
  12. Web容器初始化过程
  13. cvte前端笔试后的js原型总结
  14. CloudStack和OpenStack该如何选择(如果准备选择OpenStack,请做好hack的准备。CloudStack的底层功能已经做的很完善了,更适合商用)
  15. 关于视觉跟踪中评价标准的相关记录(The Evaluation of Visual Tracking Results on OTB-100 Dataset)
  16. 【WPF】右键菜单ContextMenu可点击区域太小的问题
  17. vscode 运行起来
  18. pyenv 让 python 版本完美切换
  19. The type org.springframework.context.support.AbstractApplicationContext cannot be resolved
  20. Caused by: java.sql.SQLException: Failed to start database 'metastore_db' with class loader org.apache.spark.sql.hive.client.IsolatedClientLoader$$anon$1@d7c365, see the next exception for details.

热门文章

  1. Spark RDD的fold和aggregate为什么是两个API?为什么不是一个foldLeft?
  2. lufylegend:图形变形3
  3. RabbitMQ的应用场景以及基本原理介绍(转)
  4. could not connect to server: Connection refused (0x0000274D/10061)
  5. Android Initializing a Build Environment
  6. log4j1修改DailyRollingFileAppender支持日志最大数量
  7. 同一个账号启动两个resin而要求使用不同jdk的解决方法
  8. tsung执行时报Can't locate Template.pm的解决
  9. SVG.js 基础图形绘制整理(一)
  10. npm出错的解决方案