java并发编程 性能与伸缩_java并发编程(十): 性能与可伸缩性

性能与可伸缩性:

对性能的思考:

提升性能意味着用更少的资源做更多的事情。

资源:CPU, 内存,I/O带宽,网络带宽,数据库请求,磁盘空间等。

性能与可伸缩性:

应用程序性能的衡量指标:服务时间,延迟时间,吞吐率,效率,可伸缩性及容量等。

可伸缩性指:增加资源时,程序的吞吐量或者处理能力相应地增加。

评估各种性能权衡因素:

避免不成熟地优化,首先使程序正确,然后再提高运行速度--如果它还运行得不够快。

以测试为基准,不要猜测。

Amdahl定律:

在增加计算资源的情况下,程序在理论上能够实现最高加速比,这个值取决于程序中可并行组件与串行组件的比重。

见图:

0818b9ca8b590ca3270a3433284dd417.png

对任务队列的串行访问

/**

* 对任务队列的串行访问

*/

public class WorkThread extends Thread {

private final BlockingQueue queue;

public WorkThread(BlockingQueue queue){

this.queue = queue;

}

public void run(){

while (true){

try {

Runnable task = queue.take(); //此处为程序的串行部分

task.run();

} catch (InterruptedException e) {

break;

}

}

}

}

在所有并发程序中都包含一些串行部分,如果你认为你的程序中不存在串行部分,那么可以再仔细检查一遍。

在各种框架中隐藏的串行部分:

不同同步队列吞吐量差异

0818b9ca8b590ca3270a3433284dd417.png

Amdahl定律的应用:

降低锁粒度的技术:锁分解(1个锁分解为2个锁),锁分段(1个锁分解为多个锁)。

线程引入的开销:

对于为了提升性能而引入的线程来说,并行带来的性能提升必须超过并发导致的开销。

上下文切换:

切换上下文需要一定的开销,而在线程调度过程中需要访问操作系统和JVM共享的数据结构。

上下文切换带来的开销因平台而异,一般情况就是几微秒。

内存同步:

同步操作的性能开销包括多个方面,在synchronized和volatile提供的可见性保证中可能会使用一些特殊命令,即内存栅栏。

在内存栅栏中,大多数操作都是不能被重排序的。

JVM可以通过优化来去掉一些不会发生竞争的锁,如:

//object只能由当前线程所访问,所以会去掉锁

synchronized(new Object()){

// do sth.

}

//局部变量v不会逃逸, 因此线程私有,优化会取消加锁

public String getStoogeNames(){

Vector v = new Vector<>();

v.add("Hello");

v.add("World");

return v.toString();

}

阻塞:

阻塞的线程将包含两次额外的上下文切换:

1. 阻塞时,cpu时间片未用完前被交换出去。

2. 请求的锁或资源可用时,再次被切换回来。

减少锁的竞争:

在并发程序中,对可伸缩性的最主要威胁就是独占方式的资源锁。

有3中方式可以降低锁的竞争程度:

1. 减少锁的持有时间。

2. 降低锁的请求频率。

3. 使用带有协调机制的独占锁。

减少锁的范围(“快进快出”):

较少锁的持有时间,如将锁无关的代码移出同步块。

/**

* 不必要的长时间持有锁

*/

public class AttrbuteStore {

private final Map attributes

= new HashMap();

/**

* synchronized锁住当前对象

*/

public synchronized boolean userLocationMatcher(String name, String regexp){

String key = "users." + name + ".location";

String location = attributes.get(key);

if (location == null)

return false;

else

return Pattern.matches(regexp, location);

}

}

可修改上面的方法:

public boolean userLocationMatcher(String name, String regexp){

String key = "users." + name + ".location";

String location = null;

synchronized(this){

location = attributes.get(key); //仅锁住共享对象

}

if (location == null)

return false;

else

return Pattern.matches(regexp, location);

}

更好的方式是将attributes用并发容器来实现,如ConcurrentHashMap等。

减小锁的粒度:

可通过锁分解和锁分段技术来实现。

锁分解实例:

/**

* 多个状态由一个锁来保护

*/

public class ServerStatus {

public final Set users;

private final Set queries;

...

public synchronized void addUser(String u){

users.add(u);

}

public synchronized void addQuery(String q){

queries.add(q);

}

}

将锁进行分解:

/**

* 多个状态由多个锁来保护

*/

public class BetterServerStatus {

public final Set users;

private final Set queries;

...

public void addUser(String u){

synchronized(users){

users.add(u);

}

}

public void addQuery(String q){

synchronized(queries){

queries.add(q);

}

}

}

锁分段:

将所分解技术进一步扩展为对一组独立对象上的锁进行分解,这种情况被称为锁分段。

避免热点域:

热点域:比如ConcurrentHashMap.size()求元素个数时,是通过枚举每个segment.size累加的,如果你说想单独用一个size来保存元素个数,这样size(), isEmpty()这些方法就很简单了,但同样来一个问题,size的修改会很频繁,切须进行锁保护,反而又降低性能,这时的size 就是一个热点域。

一些替代独占锁的方法:

第三种降低竞争锁的影响就是放弃使用独占锁。如并发容器,读写锁,不可变对象,原子变量。

监测CPU的利用率:

*nix下可用vmstat或mpstat, windows下可用perfmon查看cpu状况。

cpu利用不充分的原因:

1. 负载不充足。

2. I/O密集。*nix可用iostat, windows用perfmon。

3. 外部限制。如数据库服务,web服务等。

4. 锁竞争。可通过jstack等查看栈信息。

向对象池说"不":

通常,对象分配操作的开销比同步的开销更低。

Map性能比较:

0818b9ca8b590ca3270a3433284dd417.png

建议自行测试一番。

减少上下文切换的开销:

任务在阻塞与运行状态间转换时,就相当于一次上下文切换。

在任务执行中,尽量减少其服务时间,如一些I/O操作尽量移出同步块,这样可以缩短任务的平均服务时间,从而提高吞吐量。

完。

不吝指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值