java并发之如何解决线程安全问题
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
java并发之如何解决线程安全问题并发(concurrency)⼀个并不陌⽣的词,简单来说,就是cpu在同⼀时刻执⾏多个任务。
⽽Java并发则由多线程实现的。
在jvm的世界⾥,线程就像不相⼲的平⾏空间,串⾏在虚拟机中。
(当然这是⽐较笼统的说法,线程之间是可以交互的,他们也不⼀定是串⾏。
)多线程的存在就是压榨cpu,提⾼程序性能,还能减少⼀定的设计复杂度(⽤现实的时间思维设计程序)。
这么说来似乎线程就是传说中的银弹了,可事实告诉我们真正的银弹并不存在。
多线程会引出很多难以避免的问题,如死锁,脏数据,线程管理的额外开销,等等。
更⼤⼤增加了程序设计的复杂度。
但他的优点依旧不可替代。
死锁和脏数据就是典型的线程安全问题。
简单来说,线程安全就是:在多线程环境中,能永远保证程序的正确性。
只有存在共享数据时才需要考虑线程安全问题。
java内存区域:
其中,⽅法区和堆就是主要的线程共享区域。
那么就是说共享对象只可能是类的属性域或静态域。
了解了线程安全问题的⼀些基本概念后,我们就来说说如何解决线程安全问题。
我们来从⼀个简单的servlet⽰例来分析:
public class ReqCounterServlet extends HttpServlet{
private int count = 0;
public void doGet(HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
count++;
System.out.print("当前已达到的请求数为" + count);
}
public void doPost(HttpServletRequest request,
HttpServletResponse response) throws IOException, ServletException {
// ignore
}
}
1. 了解业务场景的线程模型
这⾥的线程模型指的是: 在该业务场景下,可能出现的线程调⽤实况。
众所周知,Servlet是被设计为单实例,在请求进⼊tomcat后,由Connector建⽴连接,再讲请求分发给内部线程池中的Processor,
此时Servlet就处于⼀个多线程环境。
即如果存在⼏个请求同时访问某个servlet,就可能会有⼏个线程同时访问该servlet对象。
如图:
线程模型,如果简单的话,就在脑海模拟⼀下就好了,复杂的话就可以⽤纸笔或其他⼯具画出来。
2. 找出共享对象
这⾥的共享对象就很明显就是ReqCounterServlet。
3. 分析共享对象的不变性条件
不变性条件,这个名词是在契约式编程的概念中的。
不变性条件保证类的状态在任何功能被执⾏后都保持在⼀个可接受的状态。
这⾥可以引申出,不可变对象是线程安全的。
(因为不可变对象就没有不变性条件)
不变性条件则主要由对可变状态的修改与访问构成。
这⾥的servlet很简单,不变性条件⼤致可以归纳为:每次请求进⼊时count计数必须加⼀,且计数必须正确。
在复杂的业务中,类的不变性条件往往很难考虑周全。
设计的世界是险恶的,只能⼩⼼谨慎,⽤测量去证明,最⼤程度地减少错误出现的⼏率。
4. ⽤特定的策略解决线程安全问题。
如何解决的确是该流程的重点。
⽬前分三种⽅式解决:
第⼀种,修改线程模型。
即不在线程之间共享该状态变量。
⼀般这个改动⽐较⼤,需要量⼒⽽⾏。
第⼆种,将对象变为不可变对象。
有时候实现不了。
第三种,就⽐较通⽤了,在访问状态变量时使⽤同步。
synchronized和Lock都可以实现同步。
简单点说,就是在你修改或访问可变状态时加锁,独占对象,让其他线程进不来。
这也算是⼀种线程隔离的办法。
(这种⽅式也有不少缺点,⽐如说死锁,性能问题等等)
其实有⼀种更好的办法,就是设计线程安全类。
《代码⼤全》就有提过,问题解决得越早,花费的代价就越⼩。
是的,在设计时,就考虑线程安全问题会容易的多。
⾸先考虑该类是否会存在于多线程环境。
如果不是,则不考虑线程安全。
然后考虑该类是否能设计为不可变对象,或者事实不可变对象。
如果是,则不考虑线程安全
最后,根据流程来设计线程安全类。
设计线程安全类流程:
1、找出构成对象状态的所有变量。
2、找出约束状态变量的不变性条件。
3、建⽴对象状态的并发访问管理策略。
有两种常⽤的并发访问管理策略:
1、java监视器模式。
⼀直使⽤某⼀对象的锁来保护某状态。
2、线程安全委托。
将类的线程安全性委托给某个或多个线程安全的状态变量。
(注意多个时,这些变量必须是彼此独⽴,且不存在相关联的不变性条件。
)
同步策略(⽂档化很重要):
定义了如何在不违背对象不变条件或后验条件的情况下对其状态的访问操作进⾏协同。
同步策略规定了如何将不可变性,线程封闭,与加锁机制等结合起来以维护线程的安全性,并且还规定了哪些变量由哪些锁保护。