1. 什么是线程安全性

调用一个函数(假设该函数是正确的)操作某对象常常会使该对象暂时陷入不可用的状态(通常称为不稳定状态),等到操作完全结束,该对象才会重新回到完全可用的状态。如果其他线程企图访问一个处于不可用状态的对象,该对象将不能正确响应从而产生无法预料的结果,如何避免这种情况发生是线程安全性的核心问题。

  1. 一个类在可以被多个线程安全调用时就是线程安全的。
  2. 类要成为线程安全的,首先必须在单线程环境中有正确的行为。
  3. 正确性与线程安全性之间的关系非常类似于在描述ACID(原子性、一致性、独立性和持久性)事务时使用的一致性与独立性之间的关系:从特定线程的角度看,由不同线程所执行的对象操作是先后(虽然顺序不定)而不是并行执行的。

考虑下面的代码片段,它迭代一个Vector中的元素。尽管Vector 的所有方法都是同步的,但是在多线程的环境中不做额外的同步就使用这段代码仍然是不安全的,因为如果另一个线程恰好在错误的时间里删除了一个元素,则get() 会抛出一个ArrayIndexOutOfBoundsException 。

Vector v = new Vector();
// contains race conditions -- may require external synchronization
for (int i=0; i<v.size(); i++) {
doSomething(v.get(i));
}

2. 线程安全性分类

Bloch 给出了描述五类线程安全性的分类方法:不可变、线程安全、有条件线程安全、线程兼容和线程对立。

  1. 不可变
    不可变的对象一定是线程安全的,并且永远也不需要额外的同步。因为一个不可变的对象只要构建正确,其外部可见状态永远也不会改变,永远也不会看到它处于不一致的状态。Java 类库中大多数基本数值类如Integer、String和BigInteger都是不可变的。
  2. 线程安全
    由类的规格说明所规定的约束在对象被多个线程访问时仍然有效,不管运行时环境如何排列,线程都不需要任何额外的同步。这种线程安全性保证是很严格的——许多类,如Hashtable 或者 Vector 都不能满足这种严格的定义。
  3. 有条件的线程安全
    有条件的线程安全类对于单独的操作可以是线程安全的,但是某些操作序列可能需要外部同步。条件线程安全的最常见的例子是遍历由 Hashtable 或者 Vector 或者返回的迭代器——由这些类返回的 fail-fast 迭代器假定在迭代器进行遍历的时候底层集合不会有变化。为了保证其他线程不会在遍历的时候改变集合,进行迭代的线程应该确保它是独占性地访问集合以实现遍历的完整性。通常,独占性的访问是由对锁的同步保证的——并且类的文档应该说明是哪个锁(通常是对象的内部监视器(intrinsic monitor))。
  4. 线程兼容
    线程兼容类不是线程安全的,但是可以通过正确使用同步而在并发环境中安全地使用。这可能意味着用一个synchronized 块包围每一个方法调用,或者创建一个包装器对象,其中每一个方法都是同步的(就像 Collections.synchronizedList() 一样)。也可能意味着用synchronized 块包围某些操作序列。为了最大程度地利用线程兼容类,如果所有调用都使用同一个块,那么就不应该要求调用者对该块同步。这样做会使线程兼容的对象作为变量实例包含在其他线程安全的对象中,从而可以利用其所有者对象的同步。
    许多常见的类是线程兼容的,如集合类 ArrayList 和 HashMap 、java.text.SimpleDateFormat 、或者 JDBC 类 Connection 和 ResultSet 。
  5. 线程对立
    线程对立类是那些不管是否调用了外部同步都不能在并发使用时安全地呈现的类。线程对立很少见,当类修改静态数据,而静态数据会影响在其他线程中执行的其他类的行为,这时通常会出现线程对立。线程对立类的一个例子是调用 System.setOut() 的类。

3. Servlet的线程安全性

Servlet/JSP默认是以多线程模式执行的,所以,在编写代码时需要非常细致地考虑多线程的安全性问题。

无状态Servlet,表示Servlet不包含域,也没有引用其它类的域,一次特定计算的瞬时状态,会唯一的存储在本地变量中,这些本地变量存在线程的栈中,只有执行线程才能访问,一个执行该Servlet的线程不会影响访问同一个Servlet的其它线程的计算结果,因为两个线程不共享状态,他们如同在访问不同的实例。无状态Servlet是线程安全的。

有状态Servlet,就是有数据存储功能。就是有实例变量的对象,可以保存数据,有状态Servlet是非线程安全的。

import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; public class StatefulServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
int result = 0; public StatefulServlet() {
super();
} protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String s1 = request.getParameter("num1");
String s2 = request.getParameter("num2");
if (s1 != null && s1 != null) {
result = Integer.parseInt(s1) * Integer.parseInt(s2);
}
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
PrintWriter out = response.getWriter();
out.print(result);
out.close();
}
}

上述Servlet在只有一个用户访问该Servlet时,程序会正常的运行,但当多个用户并发访问时,就可能会出现其它用户的信息显示在另外一些用户的浏览器上的问题。

4. 同步与互斥

线程通信主要通过共享访问字段或者字段引用的对象完成的,但是有可能出现两种错误:线程干扰(thread interference)和内存一致性错误(memory consistency)。用来防止这些错误的工具是同步(synchronization)。

参考:Java:多线程,线程同步,synchronized关键字的用法(同步代码块、非静态同步方法、静态同步方法)

5. 同步与volatile

Java 语言中的 volatile 变量可以被看作是一种 “程度较轻的 synchronized”;与 synchronized 块相比,volatile 变量所需的编码较少,并且运行时开销也较少,但是它所能实现的功能也仅是 synchronized 的一部分。在某些情况下,如果读操作远远大于写操作,volatile 变量还可以提供优于锁的性能优势。

具体描述请参考:Java 理论与实践: 正确使用 Volatile 变量

6. 活性

并发应用程序按照及时方式执行的能力称为活性(liveness)。一般包括三种类型的问题死锁、饿死和活锁。
6.1 死锁

是指程序运行中,多个线程竞争共享资源时可能出现的一种系统状态,每个线程都被阻塞,都不会结束,进入一种永久等待状态。

6.2 饿死

饿死(starvation)描述这样的情况:一个线程不能获得对共享资源的常规访问,并且不能继续工作,当共享资源被贪婪线程长期占有而不可用时,就会发生这样的情况。

6.3 活锁

一个线程经常对另一个线程的操作作出响应,如果另一个线程的操作也对这个线程的操作作出响应,那么就可能导致活锁(livelock)。和死锁类似,发生活锁的线程不能进行进一步操作。但是,线程没有被锁定,它只是忙于相互响应,以致不能恢复工作。

活锁可以比喻为两人在走廊中相遇。A避让的自己的左边让B通过,而B同时避让到自己的右边让A通过。发现他们仍然挡住了对方,A就避让到自己的右边,而B同时避让到了自己的左边,他们还是挡住了对方,所以就没完没了。

7. 高级并发对象

主要增加的高级并发对象有:Lock对象,执行器,并发集合、原子变量和同步器。

相关阅读:

Java:多线程,线程同步,同步锁(Lock)的使用(ReentrantLock、ReentrantReadWriteLock)

Java:多线程,使用同步锁(Lock)时利用Condition类实现线程间通信

Java:多线程,java.util.concurrent.atomic包之AtomicInteger/AtomicLong用法

《Java Concurrency》读书笔记,构建线程安全应用程序的更多相关文章

  1. Java并发读书笔记:线程安全与互斥同步

    目录 导致线程不安全的原因 什么是线程安全 不可变 绝对线程安全 相对线程安全 线程兼容 线程对立 互斥同步实现线程安全 synchronized内置锁 锁即对象 是否要释放锁 实现原理 啥是重进入? ...

  2. Java并发读书笔记:线程通信之等待通知机制

    目录 synchronized 与 volatile 等待/通知机制 等待 通知 面试常问的几个问题 sleep方法和wait方法的区别 关于放弃对象监视器 在并发编程中,保证线程同步,从而实现线程之 ...

  3. java effective 读书笔记

    java effective 读书笔记 []创建和销毁对象 静态工厂方法 就是“封装了底层 暴露出一个访问接口 ” 门面模式 多参数时 用构建器,就是用个内部类 再让内部类提供构造好的对象 枚举 si ...

  4. 深入理解Java虚拟机 -- 读书笔记(1):JVM运行时数据区域

    深入理解Java虚拟机 -- 读书笔记:JVM运行时数据区域 本文转载:http://blog.csdn.net/jubincn/article/details/8607790 本系列为<深入理 ...

  5. 单元测试之道Java版——读书笔记

    单元测试知道Java版读书笔记 首先我们必须要知道我们所写的代码,它的功能是什么,如果我们不了解代码的行为,那么也就无从测试. 我们测试的目的,是为了我们整个程序架构的稳定,代码其实就是欧文要实现功能 ...

  6. Spring实战第五章学习笔记————构建Spring Web应用程序

    Spring实战第五章学习笔记----构建Spring Web应用程序 Spring MVC基于模型-视图-控制器(Model-View-Controller)模式实现,它能够构建像Spring框架那 ...

  7. 《Effective Java》读书笔记 - 10.并发

    Chapter 10 Concurrency Item 66: Synchronize access to shared mutable data synchronized这个关键字不仅保证了同步,还 ...

  8. Java快车读书笔记

    办公自动化:OA 客户关系管理:CRM人力资源:HR 企业资源计划:ERP知识管理:KM 供应链管理:SCM企业设备管理系统:EAM 产品生命周期管理:PLM面向服务体系架构:SOA 商业智能:BI项 ...

  9. 深入Java虚拟机读书笔记第五章Java虚拟机

    Java虚拟机 Java虚拟机之所以被称之为是虚拟的,就是因为它仅仅是由一个规范来定义的抽象计算机.因此,要运行某个Java程序,首先需要一个符合该规范的具体实现. Java虚拟机的生命周期 一个运行 ...

  10. 深入Java虚拟机读书笔记第三章安全

    为什么需要安全性 Java的安全模型是其多个重要结构特点之一,它使Java成为适于网络环境的技术.Java安全模型侧重于保护终端用户免受从网络下载的.来自不可靠来源的.恶意程序(以及善于程序中的bug ...

随机推荐

  1. 浅谈分布式消息技术 Kafka

    Kafka的基本介绍Kafka是最初由Linkedin公司开发,是一个分布式.分区的.多副本的.多订阅者,基于zookeeper协调的分布式日志系统(也可以当做MQ系统),常见可以用于web/ngin ...

  2. java for语句

    //for语句 public class Test16{ public static void main(String args[]){ for (int i=0;i<10;i+=1){ if ...

  3. java中使用相对路径读取文件的写法总结 ,以及getResourceAsStream() (转)

    https://blog.csdn.net/my__sun_/article/details/74450241 读取文件的写法,相对路径 在当前的目录结构中读取test.txt的有四种写法 简单粗暴的 ...

  4. Leveldb 使用说明文档

    Leveldb 使用说明文档 原作者:Jeff Dean, Sanjay Ghemawat 翻译:乌合之众solym@sohu.com 英文原文地址https://rawgit.com/google/ ...

  5. 系统空间占用排查 tomcat超大日志catalina.out 删除 与df 状态更新

    二.catalina.out占用多达60+G 并且其他诸如catalina.2016-03-29.log等具体日期的日志文件,个别的也有10G左右,直接删除这些大个头.df看稍有改善.针对catali ...

  6. 使用Java调用JS

    import junit.framework.TestCase; import javax.script.ScriptEngine; import javax.script.ScriptEngineM ...

  7. python正则检测密码合法性

    客户系统升级,要求用户密码符合一定的规则,即:包含大小写字母.数字.符号,长度不小于8,于是先用python写了个简单的测试程序: #encoding=utf-8 #----------------- ...

  8. JavaScript Window Navigator 浏览器本身的信息

    window.navigator 对象包含有关访问者浏览器的信息. Window Navigator window.navigator 对象在编写时可不使用 window 这个前缀. Navigato ...

  9. IPsec ISAKMP(转)

    IPsec ISAKMP 2010-08-10 11:47:01 标签:IPsec 职场 休闲 ISAKMP Interne 安全连接和密钥管理协议(ISAKMP)是 IPsec 体系结构中的一种主要 ...

  10. linux下网络配置小节[from 老男孩的linux运维笔记]

    对于linux高手看似简单的网络配置问题,也许要说出所以然来也并不轻松,因此仍然有太多的初学者徘徊在门外就不奇怪了, 这里,老男孩老师花了一些时间总结了这个文档小结,也还不够完善,欢迎大家补充,交流. ...