精华帖 (1) :: 良好帖 (1) :: 新手帖 (1) :: 隐藏帖 (0)
|
|
---|---|
作者 | 正文 |
发表时间:2012-06-18
提高系统并发吞吐能力是构建高性能服务的重点和难点。通常review代码时看到synchronized是我都会想一想,这个地方可不可以优化。使用synchronized使得并发的线程变成顺序执行,对系统并发吞吐能力有极大影响,我的博文 http://maoyidao.iteye.com/blog/1149015 介绍了可以从理论上估算系统并发处理能力的方法。
那么对于必须使用synchronized的业务场景,这里提供几个小技巧,帮助大家减小锁粒度,提高系统并发能力。
初级技巧 - 乐观锁乐观锁适合这样的场景:读不会冲突,写会冲突。同时读的频率远大于写。
以下面的代码为例,悲观锁的实现:
public Object get(Object key) { synchronized(map) { if(map.get(key) == null) { // set some values } return map.get(key); } } 乐观锁的实现:
public Object get(Object key) { Object val = null; if((val = map.get(key) == null) { // 当map取值为null时再加锁判断 synchronized(map) { if(val = map.get(key) == null) { // set some value to map... } } } return map.get(key); }
中级技巧 - String.intern()乐观锁不能很好解决大量写冲突问题,但是如果很多场景下,锁实际上不是针对某个用户或者某个订单。比如一个用户必须先创建session,才能进行后面的操作。但是由于网络原因,创建用户session的请求和后续请求几乎同时达到,而并行线程可能会先处理后续请求。一般情况,需要对用户sessionMap加锁,比如上面的乐观锁。在这种场景下,使用String.inter()是一种更高效的办法。类 String 维护一个字符串池。 当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(该对象由 equals(Object) 方法确定),则返回池中的字符串。可见,当String相同时,String.intern()总是返回同一个对象,因此就实现了对同一用户加锁。由于锁的粒度局限于具体用户,使系统获得了最大程度的并发。
public void doSomeThing(String uid) { synchronized(uid.intern()) { // ... } } 高级技巧 - 类ConcurrentHashMapString.inter()的缺陷是类 String 维护一个字符串池是放在JVM perm区的,如果用户数特别多,导致放入字符串池的String不可控,有可能导致OOM错误或者过多的Full GC。怎么样能控制锁的个数,同时减小粒度锁呢?Java ConcurrentHashMap提供了一种很好的借鉴方式,将需要加锁的对象分为多个bucket,每个bucket加一个锁,伪代码如下:
Map locks = new Map(); List lockKeys = new List(); for(int number : 1 - 10000) { Object lockKey = new Object(); lockKeys.add(lockKey); locks.put(lockKey, new Object()); } public void doSomeThing(String uid) { Object lockKey = lockKeys.get(uid.hash() % lockKeys.size()); Object lock = locks.get(lockKey); synchronized(lock) { // do something } }
声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2012-06-19
有一些小技巧。但是不经常使用
|
|
返回顶楼 | |
发表时间:2012-06-19
最后修改:2012-06-19
我的理解和楼主有比较大的误差
1:并行计算的吞吐辆并不是由锁影响的,锁只是其中一个占有30%比例的因素,锁影响的是性能,而非吞吐。 并行计算的吞吐量是受到唯一个因素制约的: 加速比。 加速比又受到 锁(锁因子,锁力度,锁策略,锁模式,锁数量), 并行调度方案(负载均衡), CPU核数, CPU cache命中率来影响的。 其中锁虽然是影响并行执行效率的一个编程因素,但是在并行计算中不是所有的并行操作都会遇见锁,这与CPU的cache命中率有关,所以对于一般的业务锁的需求,并不是盲目的加锁,有时候虽然他们看上去会有并发问题,但是实际上不会命中cache也就完全不必要加锁,并且锁的数量只要符合CPU核数和线程数的公式(有兴趣可以去intel的官方网站查询)就完全产生不了负作用,而如果锁的数量超过了公式,那么是设计问题,而不是编程问题。 所以就涉及不到各种锁的模式了比如随机锁,集中锁,分布锁等,这主要因为锁的策略选择空间不大。 而并行计算的吞吐瓶颈就在于复杂平衡和cache的命中。复载平衡可以看成是一个设计问题,但是也可以通过调度算法来转化为了技术问题, 2:对于锁来说,乐观锁与悲观锁是对于并发问题的类型区分的,对于互斥类型的,大部分情况是悲观锁,而对于同步类型的则大多为乐观锁,这个选择的空间不是很大,不能通过转化锁的策略来优化什么。而对于同步问题,其实还有一种解决方案是线程局部变量(ThreadLocal)的解决方案。 |
|
返回顶楼 | |
发表时间:2012-06-19
我想知道,怎么去决定锁哪些东西。
例如一个方法:methodA 里面 会去操作 4个共享变量。 为了让这个方法methodA 达到线程安全, 是锁方法 synchronized methodA(){ } 还是锁某个共享变量 synchronized(table){ //udpate size //update list //update user } 还是锁 方法块synchronized(this){ //udpate size //update list //update user } 还有,是否每个操作 size、list、user的方法 也需要加锁。 |
|
返回顶楼 | |
发表时间:2012-06-19
diz 写道 我的理解和楼主有比较大的误差
1:并行计算的吞吐辆并不是由锁影响的,锁只是其中一个占有30%比例的因素,锁影响的是性能,而非吞吐。 并行计算的吞吐量是受到唯一个因素制约的: 加速比。 加速比又受到 锁(锁因子,锁力度,锁策略,锁模式,锁数量), 并行调度方案(负载均衡), CPU核数, CPU cache命中率来影响的。 其中锁虽然是影响并行执行效率的一个编程因素,但是在并行计算中不是所有的并行操作都会遇见锁,这与CPU的cache命中率有关,所以对于一般的业务锁的需求,并不是盲目的加锁,有时候虽然他们看上去会有并发问题,但是实际上不会命中cache也就完全不必要加锁,并且锁的数量只要符合CPU核数和线程数的公式(有兴趣可以去intel的官方网站查询)就完全产生不了负作用,而如果锁的数量超过了公式,那么是设计问题,而不是编程问题。 所以就涉及不到各种锁的模式了比如随机锁,集中锁,分布锁等,这主要因为锁的策略选择空间不大。 而并行计算的吞吐瓶颈就在于复杂平衡和cache的命中。复载平衡可以看成是一个设计问题,但是也可以通过调度算法来转化为了技术问题, 2:对于锁来说,乐观锁与悲观锁是对于并发问题的类型区分的,对于互斥类型的,大部分情况是悲观锁,而对于同步类型的则大多为乐观锁,这个选择的空间不是很大,不能通过转化锁的策略来优化什么。而对于同步问题,其实还有一种解决方案是线程局部变量(ThreadLocal)的解决方案。 你说的吞吐量其实说的是伸缩性,但是楼主明显不想说这方面的问题吧,而是一定资源条件下的吞吐量,使用减少锁粒度的办法;再者,如果固定资源已经是多核环境了,那么在这上面不能满足吞吐量的话,负载均衡就是一个伪命题,所谓的伸缩性也不存在。 对于第二点锁的描述倒是很赞同,在选择同步策略的时候,要做到的其实是让 程序语言和逻辑上的同步情况一致,不盲目地简单使用某种策略,你说的ThreadLocal其实也要符合实际的case。 |
|
返回顶楼 | |
浏览 7634 次