1.3.2 活跃度的危险
1)活跃度失败:一个活动进入某种它永远无法再执行的状态(无限循环后面的代码,死锁,饥饿,活锁)
1.3.3 性能危险
1)性能包括方面: 服务时间、响应性、吞吐量、资源消费、可伸缩性的不良表现。
2)上下文切换(Context switchs):当调度程序临时挂起当前运行的程序时,另一个程序开始运行。
1.4 线程无处不在
以下描述的这些场景,都会引发非应用程序管理线程调用应用程序代码的这种情况。
1)Timer(定时器):TimeTasks运行在由Timer管理的线程中。如果一个TimeTasks访问了其他应用程序正在访问的数据,不仅TimeTask需要线程安全的手段,其他那些同时访问这个数据的类也需要相应措施。最简单的方法就是,确保TimeTask访问的对象本身是线程安全的。
2)Servlet 和 JSPs:Servlet框架的目的是处理WEB应用部分的部署,分发来自远程HTTP客户的请求的这些基础层的业务。Servlet、JSP、Servlet Filter以及那些存储在ServletContext和HttpSesion容器中的对象,必须是线程安全的。
3)远程方法调用:RIM管理的线程会调用你的远程对象,故RIM对象要线程安全。
4)Swing和AWT
第二章 线程安全
编写线程安全的代码,本质是上对”状态“的管理,而且通常都是共享的、可变的状态。
状态:通俗地说,一个对象的状态就是它的数据,存储在状态变量中。
共享:一个变量可被多个线程访问。
可变:变量的值可在其生命周期内被改变。
无论何时,只要有多于一个的线程访问给定的状态变量,而且其中某个线程会写入该变量,此时必须使用同步来协调线程对该变量的访问。
2.1 线程安全性
1)线程安全的定义:当多个线程访问一个类时,如果不用考虑这些线程在运行时环境下的调度和交替执行,并且不需要额外的同步及在调用方代码不必作其他的协调,这个类的行为仍然是正确的,那么称这个类是线程安全的。
2)无状态对象永远是线程安全的。(多数的Servlet都可以设置为无状态的)
2.2 原子性
1)++i,非原子性,它包含读改写三个操作:获得当前值,加一,写回新值。
2)竞争条件:常见的一种为,”检查后再运行“——使用潜在的过期观察值来作决策或者是执行计算。它的常见用法是惰性初始化。(未加锁的懒汉模式)
3)原子操作:假设有操作A和B,如果从执行线程A的角度来看,当其他线程执行B时, 要么B全部执行完成,要么B一点都没执行,这样A和B互为原子操作。一个原子操作是指,该操作对于所有的操作,包括它自己,都满足上述的条件。上述的”检查再运行“和自增,都是复杂操作。
4)原子变量类:在java.util.concurrent.atomic中,这些类用来实现数字和对象引用的原子状态转换。
2.3 锁
1)内部锁/监视器锁:每个Java对象都可以隐式地扮演一个用于同步的角色,这些内置的锁就称为内部锁或监视器锁,内部锁在Java中是互斥锁。
2)对于每个可被多个线程访问的可变状态变量,如果所有访问它的线程在执行时都占有同一个锁,这种情况下,我们称这个变量是由这个锁保护的。
3)对于每一个涉及多个变量的不变约束,需要同一个锁保护其所有的变量。
第三章 共享对象
1)最低限安全性: 当一个线程在没有同步的情况下读取变量,它可能会得到一个过期值。但是至少可以保证它可以看到线程在那里设定的一个真实数值,而不是凭空而来的值。这种安全保证就称为最低限安全性。
2)最低限安全性应用于所有的变量,除了没有声明为volatile的64位变量(double,long),因为JVM允许将64位的读和写划分为两个32位的操作。
3)当访问一个共享的可变变量时,为什么要求所有线程都用同一个锁来同步?为了保证一个线程对数值的写入,其他线程也可见。
4)使用volatile:只有当volatile能够简化实现和同步策略的验证时,才使用。当验证正确性必须推断可见性问题时,应该避免使用。正确使用的方式包括:用于确保它们所引用的对象状态的可见性,或者用于标识重要的生命周期事件(例如初始化和关闭)的发生。
5)使用volatile的准则:1、写入变量时并不依赖变量的当前值;或者能够确保只有单一的线程修改变量的值。2、变量不需要与其他的状态变量共同参与不变约束。3、访问变量时,没有其他的原因需要加锁。
3.2 发布和逸出
1)发布:发布一个对象的意思是使它能够被当前范围之外的代码所使用。最常见的发布对象方式是将一个对象的引用存储到公共静态域中。
2)逸出:一个对象在尚未准备好时就将它发布。
3)导致this引用逸出的场景:1、在构造函数中启动一个线程。在构造函数中创建线程并没有错,但最好不要立刻启动它,取而代之的是,发布一个start()或者initialize()方法来启动对象拥有的线程。2、在构造函数中调用一个可覆盖的(非private、final)的实例方法。
4)若想在构造函数中注册监听器或启动线程,可以使用一个私有的构造函数和一个公共的工厂方法,防止this的逸出。
3.3 线程封闭
1)介绍:实现线程安全的最简单方式之一。当对象封闭在一个线程里,这种做法会自动成为线程安全的,即使对象本身不是。
2)应用:1、Swing。Swing的可视化组件和数据模型对象不是线程安全的,它是通过将它们限制到Swing的事件分发线程中,实现线程安全的。很多Swing的并发错误都滋生于从其他线程中错误地使用这些被限制的对象。
2、应用池化的JDBC Connection对象。JDBC规范中并没有要求Connection是线程安全的,在典型的服务器应用中,线程总是从池中获得一个Connection对象,并且用它处理一个单一的请求。而且在Connection对象在被归还之前,池不会将它分配给其他线程。这种连接管理模式隐式地将Connection对象限制于处于请求处理期间的线程中。
3)Ad-hoc线程限制(未经过设置而得到的线程封闭行为):指维护线程限制性的任务全部落在实现上的这种情况,这种方式是非常容易出错的,故应该有节制地使用。如果可以的话,用一种线程限制的强形式(栈限制或者ThreadLocal)取代它。
4)栈限制:线程限制的一种特例,在栈限制中,只能通过本地变量才可以触及对象。它与3)中的限制相比,更易维护,更健壮。对于基本类型的本地变量,无法用栈限制。由于无法获得基本类型的引用,故语义语法确保了基本类型本地变量总是封闭的。
5)ThreadLocal:ThreadLocal(线程本地)变量通常用于防止在基于可变的单体(Singleton)或全局变量的设计中,出现(不正确的)共享。譬如用ThreadLocal存储Connection(JDBC的连接)。Threadlocal变量会降低重用性,引入隐晦的类间的耦合,故需要谨慎使用。
3.4 不可变性
为了满足同步的需求,另一种方法是使用不可变对象。
1)不可变对象:创建后状态不可改变的对象,它们的常量(域)在构造函数中创造。不可变对象是天生线程安全的。
2)不可变对象的满足条件:1,状态不能在创建后被修改;2,所有域都是final类型的,并且它被正确的创建(创建过程中没有this的逃逸)(从技术上来讲,不可变对象的域不完全声明为Final也可以。例如String类。P47)
PS:所有域声明为final的对象仍然可能是可变的,因为final域可以获取到一个可变对象的引用。
3)对象是不可变的≠对象的引用是不可变的。
4)程序存储在不可变对象的状态中仍然可以通过替换一个带有新状态的不可变对象的实例得到更新。
3.5 安全发布 (没看懂 P51)
1)不正确发布导致的两种错误:1.发布线程以外的所有线程都可以看到holder域(案例中错误发布的类)的过期值。2,线程看到的holder引用是最新的,但是Holder状态是过期的。
2)不可变对象与初始化安全性:出于不可变对象的重要性,Java存储模型为共享不可变对象提供了特殊的初始化安全性的保证。不可变对象可以在没有额外同步的情况下,安全的用于各种线程;甚至发布它们时也不需要同步。(注意,如果final域中指向可变对象,仍然需要同步)
3)安全发布的条件(前提,对象被正确创建):1,通过静态初始化器初始化对象的引用。2,将它的引用存储到volatile域或AtomicReference中。3,将它的引用存储到正确创建的对象的final域中,或将引用存储到由锁正确保护的域中。
4)Java中安全的容器:1,Map有Hashtable、synchronizedMap、ConcurrentMap。2,List和Set有Vector、CopyOnWriteArraylist、ConpyOnWritArraySet 、SynchronizedList 和 SynchronizedSet。3,Queue有BlockedQueue和ConcurrentQueue。
5)通常,以最简单和最安全的方式发布一个被静态创建的对象,就是使用静态初始化器。静态初始化器由JVM在类的初始阶段执行,由于JVM内在的同步,故保证了安全发布。‘
6)高效不可变对象:一个在技术上不是不可变的,但是在发布后的状态不会被修改的对象。这种对象不必满足在3.4节中提出来的不可变形的约束条件,这些对象发布后只要简单地把它们当做不可变对象即可。
举例:Date自身是可变的,假设我现在正在维护一个Map,里面存储了每个用户的最近登陆时间,若Date在放入之后就不再改变,而此时SynchronizedMap内部是同步的,故访问Date时不用再额外地再做同步。此时Date即为上述的高效不可变对象。
7)不同可变形对象的发布:1,不可变对象可以通过任意机制发布;2,高效不可变对象必须安全发布;3,可变对象必须安全发布,同时必须是线程安全的或是被锁保护的。
8)共享对象的策略:1,线程限制:一个被线程限制的对象,只能被本线程修改和访问;
2,共享只读:一个共享的只读对象,在没有额外同步的情况下,不能被任何线程修改。共享只读对象包括可变对象和高效不可变对象;
3,共享线程安全:一个线程安全的对象在内部进行同步,所有其他的线程不需要对其进行额外的同步,通过公共接口可随意访问;
4,被守护的:一个被守护的对象只能通过特定的锁来访问。被守护的对象包括那些被线程安全对象封装的对象,和已知被特定的锁保护起来的已发布对象。
第四章 组合对象
4.1 设计线程安全的类
1)设计的3个基本要素:1,确定对象状态是由哪些变量构成的;2,确定限制状态变量的不变约束;3,制定一个管理并访问对象状态的策略。