java并发之如何解决线程安全问题

并发(concurrency)一个并不陌生的词,简单来讲,就是cpu在同一时刻执行多个任务。java

而Java并发则由多线程实现的。编程

在jvm的世界里,线程就像不相干的平行空间,串行在虚拟机中。(固然这是比较笼统的说法,线程之间是能够交互的,他们也不必定是串行。)tomcat

多线程的存在就是压榨cpu,提升程序性能,还能减小必定的设计复杂度(用现实的时间思惟设计程序)。安全

这么说来彷佛线程就是传说中的银弹了,可事实告诉咱们真正的银弹并不存在。多线程

多线程会引出不少难以免的问题, 如死锁,脏数据,线程管理的额外开销,等等。更大大增长了程序设计的复杂度。并发

但他的优势依旧不可替代。jvm

 

死锁脏数据就是典型的线程安全问题。工具

简单来讲,线程安全就是: 在多线程环境中,能永远保证程序的正确性。性能

只有存在共享数据时才须要考虑线程安全问题。 spa

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均可以实现同步。简单点说,就是在你修改或访问可变状态时加锁,独占对象,让其余线程进不来。

这也算是一种线程隔离的办法。(这种方式也有很多缺点,好比说死锁,性能问题等等)

 

 

 

 

其实有一种更好的办法,就是设计线程安全类。《代码大全》就有提过,问题解决得越早,花费的代价就越小。

是的,在设计时,就考虑线程安全问题会容易的多。

首先考虑该类是否会存在于多线程环境。若是不是,则不考虑线程安全。

而后考虑该类是否能设计为不可变对象,或者事实不可变对象。若是是,则不考虑线程安全

最后,根据流程来设计线程安全类。

设计线程安全类流程:

一、找出构成对象状态的全部变量。

二、找出约束状态变量的不变性条件。

三、创建对象状态的并发访问管理策略。

 

有两种经常使用的并发访问管理策略:

一、java监视器模式。  一直使用某一对象的锁来保护某状态。

二、线程安全委托。  将类的线程安全性委托给某个或多个线程安全的状态变量。(注意多个时,这些变量必须是彼此独立,且不存在相关联的不变性条件。) 

 

同步策略(文档化很重要):

  定义了如何在不违背对象不变条件或后验条件的状况下对其状态的访问操做进行协同。同步策略规定了如何将不可变性,线程封闭,与加锁机制等结合起来以维护线程的安全性,而且还规定了哪些变量由哪些锁保护。

相关文章
相关标签/搜索