格言:在程序猿界混出点名堂!
![](https://img.haomeiwen.com/i18759109/f022812d61509771.png)
《JAVA并发编程实战》解读
【连载】第1章-1.3线程带来的风险
回顾:在1.2中介绍线程的优势,凡事必有两面性,这一节,本书的作者给我们罗列了线程的风险点。
安全性问题
直接看下面的经典例子i++
@NotThreadSafe
public class UnsafeSequence{
private int value;
/**返回一个唯一的数值*/
public int getNext(){
return value++;
}
}
- 解说
上面的例子是调用getNext()
就会获取一个唯一序号,单线程不存在问题,但是多线程下就存在问题,因为i++
非原子操作,i++
分解为三步:获取、修改、设置。不同线程交替执行三步,比如:
A获取0,B获取0,A修改为1,B修改为1,A更新为1,B更新为1。原本两个线程调用,最终结果应该为2,由于存在竞态
,导致结果超出预料。
如果考虑指令重排及线程内存与主存的同步等问题,实际情况可能更复杂。
- 如何解决
还好Java提供了各种同步机制来协同
这种访问。
@ThreadSafe
public class UnsafeSequence{
@GuardedBy("this") private int value;
/**返回一个唯一的数值*/
public synchronized int getNext(){
return value++;
}
}
synchronized关键字会保证,getNext()
的代码保证一次只有一个线程执行,并且执行完成后,将value同步到主存。
活跃性问题
活跃性问题为多线程导致的死锁、饥饿、活锁,依赖于不同线程事件发生的时序。比如,经典的饥饿的例子就是A持有B的锁,B持有A的锁,导致死锁。代码来说话:
@DeadLock
public class TranService{
public void tranMoney(Account from,Account to){
synchronized(from){
synchronized(to){
from.withdraw();
to.deposit();
}
}
}
public static void main(String[] args){
Account a=...,b=...;
TranService tranService=..;
// 某系统发起a向b转账
new Thread(()->{
tranService.tranMoney(a,b);
}).start();
// 某系统发起b向a转账
new Thread(()->{
tranService.tranMoney(b,a);
}).start();
}
}
- 解读
虽然看似tranMoney
方法不会导致死锁,但是客户端,传递的对象即也是锁的顺序不一致,导致可能产生死锁问题。
性能问题
多线程虽然能利用多核优势提升整体性能,但同样也会带来性能问题。
- 不合理的多线程使用,造成cpu频繁的上下文切换(Context Switch),可使用linux中的
vmstat
中的cs
来查看。因为CPU更多的花在线程调度上而不是运行上,比如JVM的程序计数器会频繁记录线程指令的执行位置,造成不必要的资源浪费。 - 使用同步机制(Synchronized)往往会抑制编译器的优化,是内存缓存的数据无效,增加共享内存总线的同步流量。我理解这句话的意思是:不使用同步机制,本来线程是将数据先写到线程的缓存然后在同步到主存,使用之后,直接写到主存,增加频繁同步带来的消耗。类似一种批量写的缓存机制。
知识点
- 线程带来的问题。(活跃性/安全性/性能)
- 理解术语:
活跃性
、竞态
、协同
。 - i++的理解。
- 死锁的产生原理。
- synchronized的初步了解。
喜欢连载可关注微信公众号:逗哥聊IT。
![](https://img.haomeiwen.com/i18759109/2c0873d7036b79fc.png)
网友评论