JAVA并发编程实践笔记
1, 保证线程安全的三种方法:
a, 不要跨线程访问共享变量
b, 使共享变量是final类型的
c, 将共享变量的操作加上同步
2, 一开始就将类设计成线程安全的, 比在后期重新修复它,更容易.
3, 编写多线程程序, 首先保证它是正确的, 其次再考虑性能.
4, 无状态或只读对象永远是线程安全的.
5, 不要将一个共享变量裸露在多线程环境下(无同步或不可变性保护)
6, 多线程环境下的延迟加载需要同步的保护, 因为延迟加载会造成对象重复实例化
7, 对于volatile声明的数值类型变量进行运算, 往往是不安全的(volatile只能保证可见性,不能保证原子性).
详见volatile原理与技巧中, 脏数据问题讨论.
8, 当一个线程请求获得它自己占有的锁时(同一把锁的嵌套使用), 我们称该锁为可重入锁.
在jdk1.5并发包中, 提供了可重入锁的java实现-ReentrantLock.
9, 每个共享变量,都应该由一个唯一确定的锁保护.
创建与变量相同数目的ReentrantLock, 使他们负责每个变量的线程安全.
10,虽然缩小同步块的范围, 可以提升系统性能.
但在保证原子性的情况下, 不可将原子操作分解成多个synchronized块.
11, 在没有同步的情况下, 编译器与处理器运行时的指令执行顺序可能完全出乎意料.
原因是, 编译器或处理器为了优化自身执行效率, 而对指令进行了的重排序(reordering).
12, 当一个线程在没有同步的情况下读取变量, 它可能会得到一个过期值, 但是至少它可以看到那个线程在当时设定的一个真实数值. 而不是凭空而来的值. 这种安全保证, 称之为最低限的安全性(out-of-thin-air safety)
在开发并发应用程序时, 有时为了大幅度提高系统的吞吐量与性能, 会采用这种无保障的做法.
但是针对, 数值的运算, 仍旧是被否决的.
13, volatile变量,只能保证可见性, 无法保证原子性.
详见 volatile原理与技巧
14, 某些耗时较长的网络操作或IO, 确保执行时不要占有锁.
15, 发布(publish)对象, 指的是使它能够被当前范围之外的代码所使用.(引用传递)
对象逸出(escape), 指的是一个对象在尚未准备好时将它发布.
原则: 为防止逸出, 对象必须要被完全构造完后, 才可以被发布(最好的解决方式是采用同步)
this关键字引用对象逸出
例子: 在构造函数中, 开启线程, 并将自身对象this传入线程, 造成引用传递.
而此时, 构造函数尚未执行完, 就会发生对象逸出了.
16, 必要时, 使用ThreadLocal变量确保线程封闭性(封闭线程往往是比较安全的, 但一定程度上会造成性能损耗)
封闭对象的例子在实际使用过程中, 比较常见, 例如 hibernate openSessionInView机制, jdbc的connection机制.
17, 单一不可变对象往往是线程安全的(复杂不可变对象需要保证其内部成员变量也是不可变的)
良好的多线程编程习惯是: 将所有的域都声明为final, 除非它们是可变的
18, 保证共享变量的发布是安全的
a, 通过静态初始化器初始化对象(jls 12.4.2叙述, jvm会保证静态初始化变量是同步的)
b, 将对象申明为volatile或使用AtomicReference
c, 保证对象是不可变的
d, 将引用或可变操作都由锁来保护
19, 设计线程安全的类, 应该包括的基本要素:
a, 确定哪些是可变共享变量
b, 确定哪些是不可变的变量
c, 指定一个管理并发访问对象状态的策略
20, 将数据封装在对象内部, 并保证对数据的访问是原子的.
建议采用volatile javabean模型或者构造同步的getter,setter.
21, 线程限制性使构造线程安全的类变得更容易, 因为类的状态被限制后, 分析它的线程安全性时, 就不必检查完整的程序.
22, 编写并发程序, 需要更全的注释, 更完整的文档说明.
23, 在需要细分锁的分配时, 使用java监视器模式好于使用自身对象的监视器锁.
前者的灵活性更好.
Object target = new Object();
// 这里使用外部对象来作为监视器, 而非this
synchronized(target) {
// TODO
}
针对java monitor pattern, 实际上ReentrantLock的实现更易于并发编程.
功能上, 也更强大.
24, 设计并发程序时, 在保证伸缩性与性能折中的前提下, 优先考虑将共享变量委托给线程安全的类.
由它来控制全局的并发访问.
25, 使用普通同步容器(Vector, Hashtable)的迭代器, 需要外部锁来保证其原子性.
原因是, 普通同步容器产生的迭代器是非线程安全的.
26, 在并发编程中, 需要容器支持的时候, 优先考虑使用jdk并发容器
(ConcurrentHashMap, ConcurrentLinkedQueue, CopyOnWriteArrayList...).
27, ConcurrentHashMap, CopyOnWriteArrayList
并发容器的迭代器,以及全范围的size(), isEmpty() 都表现出弱一致性.
他们只能标示容器当时的一个数据状态. 无法完整响应容器之后的变化和修改.
28, 使用有界队列, 在队列充满或为空时, 阻塞所有的读与写操作. (实现生产-消费的良好方案)
BlockQueue下的实现有LinkedBlockingQueue与ArrayBlockingQueue, 前者为链表, 可变操作频繁优先考虑,后者为数组, 读取操作频繁优先考虑.
PriorityBlockingQueue是一个按优先级顺序排列的阻塞队列, 它可以对所有置入的元素进行排序(实现Comparator接口)
29, 当一个方法, 能抛出InterruptedException, 则意味着, 这个方法是一个可阻塞的方法, 如果它被中断, 将提前结束阻塞状态.
当你调用一个阻塞方法, 也就意味着, 本身也称为了一个阻塞方法, 因为你必须等待阻塞方法返回.
如果阻塞方法抛出了中断异常, 我们需要做的是, 将其往上层抛, 除非当前已经是需要捕获异常的层次.
如果当前方法, 不能抛出InterruptedException, 可以使用Thread.currentThread.interrupt()方法, 手动进行中断.
JAVA并发编程实践笔记2
《Java并发编程实践》笔记
1)父线程VS子线程:JVM要等到所有用户线程都结束后才会终止,或者调用System.exit()方法强制终止JVM。如果只有守护线程在运行,则JVM将会自动终止。在java中,父线程和子线程只是在运行时谁创建谁的关系。一旦线程被启动,这2个线程平等的没有父子关系,父线程一般结束不会影响子线程的运行。
2)线程会共享进程范围内的资源
======================================================================
线程的优势:
1)发挥多处理器的强大功能
2)建模的简单性
如果在程序中只包含一种类型的任务,那么比包含多种不同类型任务的程序要更易于变现,错误更少,也更容易测试。如果为模型中每种类型的任务都分配一个专门的线程,那么可以形成一种串行执行的假象,并将程序的执行逻辑与[调度机制的细节、交替执行的操作、异步I/O、资源等待等问题]分离开来。通过使用线程,可以将负责并且异步的工作流进一步分解为一组简单并且同步的工作流,每个工作流在一个单独的线程中运行,并在特定的同步位置进行交互。【典型例子:Servlet】
3)异步事件的简化处理
4)响应更灵敏的用户界面。【典型例子:Java里的AWT和Swing工具,采用一个事件分发线程代替主事件循环】
线程带来的风险:
1)安全性问题:多个线程共享相同的内存地址空间
2)活跃性问题:如何避免死锁、饥饿、活锁。。。,依赖不同线程的事件发生时序
3)性能问题:线程调度、同步机制带来的性能开销
线程无处不在:
每个Java应用程序都会使用线程。当JVM启动时,它将为JVM的内部任务(例如:垃圾收集、终结操作等)创建后台线程,并创建一个主线程来运行main方法。AWT和Swing的UI框架将创建线程来管理用户界面事件。Timer将创建线程来执行延迟任务。一些组件框架,例如Servlet和RMI,都会创建线程并调用这些线程中的方法。
======================================================================
线程安全性:
1)要编写线程安全的代码,其核心在于要对状态访问操作进行管理,特别是对共享的和可变状态的访问。(共享且可变)【三种方法:①不在线程之间共享该状态变量;②将状态变量修改为不可变的变量;③在访问状态变量时使用同步】
2)Java中的同步机制(多个线程中指令执行前后顺序):synchronized(内置互斥独占加锁)|Object的wait|notify、volatile类型的变量、显式锁|Condition、原子变量(java.util.concurrent.atomic实现在数值和对象引用上的原子状态转换|CAS)、(静态初始化器由JVM在类的初始化阶段执行,由于在JVM内部存在同步机制)。
3)类是线程安全的:当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协助,这个类都能表现出正确地行为。——保证不变性条件不被破坏。
======================================================================
原子、加锁、可见(提交)
3.5)共享数据可见性:多线程安全根本问题所在,多个线程指令执行顺序和多级缓存造成各处变量不一致。所以①从执行顺序开始处理:Java同步机制synchronized、Object的wait|notify、volatile、AbstractQueueSynchronized,其底层是Java内存模型lock—read—load—use—assign—store—write—unlock的各种约定和原则。②是CAS非阻塞,类似一种网络协议[忘了],一直测试直到不产生碰撞冲突后才继续操作。③是ThreadLocal造成不共享。④不变性,使变量不可变就不存在可见性问题。
4)竞态条件:由于不恰当的执行时序而出现不正确的结果。【典型例子:先检查后执行】
5)复合操作:将“先检查后执行”以及“读取-修改-写入”等操作的统称:包含一组必须以原子方式执行的操作以确保线程安全性。 原子性。
6)要保持状态的一致性,就需要在单个原子操作中更新所有相关的状态变量。对于每个包含多个变量的不变性条件,其中涉及的所有变量都需要由同一个锁来保护。
8)重入:获取锁的操作的粒度是“线程”,而不是“调用”——如果某个线程试图获得一个意见由它自己持有的锁,那么这个请求就会成功。【内置锁synchronized是可重入的】
9)虽然synchronized方法可以确保单个操作的原子性,但如果要把多个操作合并为一个复合操作,还是需要额外的加锁机制。
10)加锁的含义不仅仅局限于互斥行为,还包括内存可见性。为了确保所有线程都能看到共享变量的最新值,所有执行读操作或者写操作的线程都必须在同一个锁上同步。——对象的最新状态被安全地发布。【加锁机制既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性】
11)volatile变量:确保将变量的更新操作马上通知到其他线程。【典型例子:检查某个状态标记以判断是否退出循环】。Volatile变量通常用作某个操作完成、发生中断或者状态的标志。但是volatile的语义不足以确保递增操作++的原子性。(原子变量提供了“读-改-写”的原子操作,并且常常用做一种“更好的volatile变量”)
12)“发布”一个对象:是对象能够在当前作用域之外的代码中使用。可以是以下几种情况:①将对象的引用保存到一个公有的静态变量中,以便任何类和线程都能看到该对象;②发布某个对象的时候,在该对象的非私有域中引用的所有对象都会被发布;③发布一个内部的类实例,内部类实例关联一个外部类引用。
13)逸出:某个不应该发布的对象被公布的时候。某个对象逸出后,你必须假设有某个类或线程可能会误用该对象,所以要封装。
14)不要在构造过程中使this引用逸出。【常见错误:在构造函数中启动一个线程。】
======================================================================
15)线程封闭:不共享。仅在单线程内访问数据,就不需要同步。【典型应用:①Swing的可视化组件和数据模型对象都不是线程安全的,Swing通过将它们封闭到Swing的实际分发线程中来实现线程安全;②JDBC的Connection对象】
16)线程封闭技术:①Ad-hoc线程封闭:维护线程封闭性的职责完全由程序实现来承担。I单线程子系统。在volatile变量上存在一个特殊的线程封闭:能确保只有单个线程对共享的volatile变量执行写入操作(其他线程有读取volatile变量),那么就可以安全地在这些共享的volatile变量上执行“读取-修改-写入”的操作,而其他读取volatile变量的线程也能看到最新的值。②栈封闭:通过线程的局部变量,但也要注意不能使之逸出。③ThreadLocal类
17)不变性:如果某个对象在被创建后其状态就不能被修改,那么这个对象就称为不可变对象。线程安全性是不可变对象的固有属性之一,他们的不变性条件是有构造函数创建的。当满足以下所有条件的时候,对象才是不可变的:①对象创建以后起状态就是不能修改;②对象的所有域都是final类型;③对象是正确创建的(在对象的创建期间,this引用没有移除)。
18)任何线程都可以再不需要额外同步的情况下安全的访问不可变对象,即使在发布这些对象时没有使用同步。
18)安全发布:常用模式:要安全的发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见:①在静态初始化函数中初始化一个对象引用(静态初始化器由JVM在类的初始化阶段执行,由于在JVM内部存在同步机制,所以可以安全地发布);②将对象的引用保存到volatile类型的域或者AtomicReferance对象中;③将对象的引用保存到某个正确构造对象的final类型域中;④将对象的引用保存到一个由锁保存的域中(线程安全容器)。
19)事实不可变对象
20)如果对象在构建以后可以修改,那么安全发布只能确保“发布当时”状态的可见性。对于可变对象,不仅在发布对象时需要使用同步,而且在每次对象访问时都需要使用同步来确保后续修改操作的可见性。
======================================================================
21)安全的共享对象:
①线程封闭:线程封闭的对象只能由一个线程拥有,对象被封闭在该线程中,并且只能由这个线程修改。
②只读共享:在没有额外同步的情况下,共享的只读对象可以由多个线程并发访问,但任何线程都不能修改它。共享的只读对象包括不可变对象和事实不可变对象。
③线程安全共享:线程安全的对象在其内部实现同步,因此多个线程可以通过对象的公有接口进行访问而不需要进一步的同步。
④保护对象:被保护的对象只能通过持有特定的锁来访问。包含对象包括封装在其他线程安全对象中的对象,以及已发布的并且由某个特定锁保护的对象。
======================================================================
22)设计线程安全的类
在设计线程安全类的过程中,需要包含以下3个基本要素
①找出构成对象状态的所有变量
②找出约束状态变量的不变性条件
③建立对象状态的并发访问管理策略(同步策略定义如何在不违背对象不变条件或后验条件的情况下对其状态的访问操作进行协同)
23)①由于不变性条件以及后验条件在状态及状态转换上施加了各种约束,因此需要额外的同步与封装。如果某些状态是无效的,那么必须对底层的状态变量进行封装,否则客户代码可能会使对象处于无效状态。如果在某个操作中存在无效的状态转换,那么该操作必须是原子的。②在类中也可能包含多个状态变量的不变条件:这些相关的变量必须在单个原子操作中进行读取或更新。③依赖状态的操作:先验条件
======================================================================
24)阻塞队列可以作为同步工具类,其他类型的同步工具类还包括信号量Semaphore、栅栏Barrier(java.util.concurrent.Exchanger<V>一种双向栅栏,各方在栅栏位置上交换数据,java.util.concurrent.CyclicBarrier,)、闭锁Latch(java.util.concurrent.CountDownLatch,java.util.concurrent.FutureTask<V>)
栅栏:所有线程必须同时到达栅栏位置,才能继续执行;闭锁:用于等待事件,栅栏用于等待其他线程。
JAVA并发编程实践笔记3
- 线程带来的问题:a)安全性问题b)活跃性问题c)性能问题
- 要编写线程安全的代码其核心在于要对状态访问操作进行管理,特别是对共享的和可变的状态的访问
- Java中的主要同步机制是关键字synchronized,它提供了一种独占的加锁方式,”同步”这个术语还包括volatile类型的变量,显示锁以及原子变量
- 在编写并发应用程序时,一种正确的编程方法是:首先使代码正确运行,然后在提高代码的速度。
- 完全有线程安全类构成的程序并不一定就是线程安全的,而在线程安全类中也可以包含非线程安全的类
- 线程安全性:当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的
- 无状态对象一定是线程安全的
- 无状态对象、原子性、竟态条件、符合操作
- 当某个计算的正确性取决于多个线程的交替执行时序时,那么就会发生竟态条件。最常见的竟态条件类型就是”先检查后执行”操作,即通过一个可能失效的观测结果来决定下一步的动作
- 计数器,可以通过现有的线程安全类实现如AtomicLong
- 在实际情况中,应尽可能地使用现有的线程安全对象(如AtomicLong)来管理类的状态
- 同步代码块包括两部分:一个作为锁的对象引用,一个作为由这个锁保护的代码块。
- 重入意味着获取锁的操作粒度是”线程”,而不是”调用”
- 每个共享的和可变的变量都应该只由一个锁来保护,从而是维护人员知道是那一个锁
- 一种常见的加锁约定是,将所有的可变状态都封装在对象内部,并通过对象的内置锁对所有访问可变状态的代码路径进行同步,使得在该对象上不会发生并发访问,如Vector
- 并非所有数据都需要锁的保护,只有被多个线程同时访问的可变数据才需要通过锁来保护
- 对于每个包含多个变量的不可变性条件,其中涉及的所有变量都需要由同一个锁来保护
- 无论是执行计算密集的操作,还是在执行某个可能阻塞的操作,如果持有锁的时间过长,那么都会带来活跃性或性能问题,当执行时间较长的计算或者可能无法快速完成的操作时(如I/O),一定不要持有锁
- 只要有数据在多个线程间共享,就使用正确的同步
- 可见性问题,产生失效值,非原子的64位操作问题,使用volatile声明或同步保护
- 当把变量声明为volatile类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。
- Volatile变量的正确的使用方式包括:确保它们自身状态的可见性,确保它们所引用对象的状态的可见性,以及标识一些重要的程序生命周期事件的发生(例如,初始化或关闭)
- 调试提示,在启动JVM时指定 –servcr命令,将进行更多优化,比如将循环中未被修改的变量提升到循环外部,发现无线循环
- 加锁机制既可以确保可见性又可以确保原子性,而volatile变量只能确保可见性
- 当且仅当满足一下所有条件时,才应该使用volatile变量:a)对变量的写入操作不依赖变量的当前值,或者能确保只有单个线程更新变量的值b)该变量不会与其他状态变量一起纳入不变性条件中c)在访问变量时不需要加锁
- 当访问共享的可变数据时,通常需要使用同步。一种避免使用同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步。这种技术被称为线程封闭
- 线程封闭技术:Ad-hoc/栈封闭/ThreadLocal类
- 满足同步需求的另一种方法是使用不可变对象:某个对象在被创建后其状态就不能被修改。不可变对象只有一种状态,且有构造函数来控制
- 不可变:a)状态不可修改b)所有域都是final类型c)正确的构造过程
- 可变对象必须通过安全的方式来发布,这通常意味着在发布和使用该对象的线程时都必须使用同步
- 要安全地发布一个对象,对象的引用以及对象的状态必须同时对其他线程可见。一个正确构造的对象可以通过以下方式来安全的发布:a)在静态初始化函数中初始化一个对象引用b)将对象的引用保存到volatile类型的域或者AtomicReferance对象中c)将对象的引用保存到某个正确构造对象的final类型域中d)将对象的引用保存到一个由锁保护的域中
- 通过容器安全发布对象:a)通过将一个键或者值放入Hashtable、synchronizedMap或者ConcurrentMap中b)通过将某个元素放入Vector、CopyOnWriteArrayList、CopyOnWriteArraySet、synchronizedList或synchronizedSet中c)通过将某个元素放入BlockingQuere或者ConcurrentLinkedQuere中
- 当获得对象的一个引用时,需要知道在这个引用上可以执行哪些操作。在使用它之前是否需要获得一个锁,是否可以修改它的状态,或者只能读取它
- 在并发程序中使用和共享对象时,可以使用一些实用的策略:a)线程封闭b)只读共享c)线程安全共享d)保护对象
- 在设计线程安全类的过程中,需要包含一下三个基本要素:a)找出构成对象状态的所有变量b)找出约束状态变量的不变性条件c)建立对象状态的并发访问管理策略
- 等待某个条件为真的各种内置机制(包括等待和通知等机制)都与内置加锁机制紧密关联
- 将数据封装在对象内部,可以将数据的访问限制在对象的方法上,从而更容易确保线程在访问数据时总能持有正确的锁
- 使用私有的锁对象而不是对象的内置锁(或任何其他可通过公有方式访问的锁),可以将锁封装起来,使客户代码无法得到锁,但客户代码可以通过共有方法来访问锁
- 如果一个状态变量是线程安全的,并且没有任何不变性条件来约束它的值,在变量的操作上也不存在任何不允许的状态转换,那么就可以安全的发布这个变量
- Synchronized、volatile或者任何一个线程安全类都对应于某种同步策略,用于在并发访问时确保数据的完整性
- 在设计同步策略时需要考虑多个方面,例如,将哪些变量声明为volatile类型,哪些变量用锁来保护,哪些锁保护那些变量,哪些变量必须是不可变的或者被封闭在线程中的,哪些操作必须是原子操作等。
- servletContext、Httpsession或dataSource等的线程安全性
- 同步容器将所有对容器状态的访问都穿行化,以实现它们的线程安全性。这种方法的代价是严重降低并发性,当多个线程竞争容器的锁时,吞吐量将严重减低
- 通过并发容器来代替同步容器,可以极大的提高伸缩性并降低风险;ConcurrentHashMap、CopyOnWriteArrayList、CopyonWriteArraySet、BlockingQueue
- 阻塞队列可以作为同步工具类,其他类型的同步工具类还包括信号量(Semaphore)、栅栏(Barrier)以及闭锁(Latch)
- 闭锁可以延迟线程的进度直到其到达终止状态,可以用来确保某些活动直到其他活动都完成偶才继续执行
- FutureTask表示的计算是通过Callable来实现的,相当于一种可生成结果的Runnable,FutureTask在Executor框架中表示异步任务,此外还可以用来表示一些时间较长的计算
- 计数信号量用来控制同时访问某个特定资源的操作数量,或者同时执行某个指定操作的数量,可以用于实现资源池,例如数据库连接池
- 栅栏类似于闭锁,它能阻塞一组线程直到某个事件发生。栅栏与闭锁的关键区别在于,所有线程必须同时到达栅栏位置,才能继续执行,闭锁用于等待事件,而栅栏用于等待其他线程
- 并发技巧:a)可变状态是至关重要的b)尽量将域声明为final类型,除非需要它们是可变的c)不可变对象一定是线程安全的d)封装有助于管理复杂性e)用锁来保护每个可变变量f)当保护同一个不变性条件中的所有变量时,要使用同一个锁g)在执行复合操作期间,要持有锁h)如果从多个线程中访问同一个可变变量时没有同步机制,那么程序会出现问题i)不要故作聪明的推断出不需要使用同步j)在设计过程中考虑线程安全,或者在文档中明确地指出它不是线程安全的k)将同步策略文档化
- 在线程池中执行任务比为每个任务分配一个线程优势更多:a)重用线程,分摊在线程创建和销毁过程中产生的巨大开销b)请求到达时,工作线程已存在,不会由于等待创建线程而延迟任务的执行,提高了响应性c)通过调整线程池大小,可以创建足够多的线程以便使处理器保持忙碌状态,同时还可以防止过多线程相互竞争资源而使应用程序耗尽内存或失败
- 通过使用Executor,可以实现各种调优、管理、监视、记录日志、错误报告和其他功能,如果不使用任务执行框架,那么要增加这些功能是很困难的
- Executor框架将任务提交与执行策略解耦开来,同时还支持多种不同类型的执行策略。当需要创建线程来执行任务时,可以考虑使用Executor
- 在java中没有一种安全的抢占式方法来停止线程,因此也就没有安全的抢占式方法来停止任务。只有一些协作式的机制,使请求取消的任务和代码都遵循一种协商好的协议:a)”已请求取消”标志
- 对中断操作的正确理解是:它并不会真正地中断一个正在运行的线程,而只是发出中断请求,然后由线程在下一个合适的时刻中断自己(这些时刻也被称为取消点),通常,中断是实现取消的最合理方式
- 最合理的中断策略是某种形式的线程级(Thread-Level)取消操作或服务级(Service-Level)取消操作:尽快退出,在必要时进行清理,通知某个所有者线程已经退出
- 任务不会在其自己拥有的线程中执行,而是在某个服务(如线程池)拥有的线程中执行,这就是大多数可阻塞的库函数都只是抛出interruptedException作为中断响应,它们永远不会在某个由自己拥有的线程中运行,因此它们为任务或库代码实现了最合理的取消策略:尽快退出执行流程,并把中断信息传递给调用者,从而使调用栈中的上层代码可以采取进一步的操作
- 当取消一个生产者-消费者操作时,需要同时取消生产者和消费者
- 关闭钩子是指通过Runtime.addShutdownHook注册的但尚未开始的线程
- 线程可分为两种:普通线程和守护线程。在JVM启动时创建的所有线程中,除了主线程以外,其他的线程都是守护线程(例如垃圾回收器以及其他执行辅助工作的线程)。当创建一个新线程时,新线程将继承它的线程的守护状态,因此在默认情况下,主线程创建的所有线程都是普通线程。普通线程与守护线程之间的差异仅在于当线程退出时发生的操作
- 死锁:过度加锁可能导致”锁顺序死锁”,使用线程池和信号量来限制对资源的使用,可能会导致”资源死锁”
- 在并发程序中,对可伸缩性的最主要威胁就是独占方式的资源锁
- 有两个因素将影响在锁上发生竞争的可能性:锁的请求频率,以及每次持有该锁的时间
- Amdahl定律告诉我们,程序的可伸缩性取决于在所有代码中必须被串行执行的代码比例
- 提升可伸缩性可通过:减少锁的持有时间,降低锁的粒度,以及采用非独占的锁或非阻塞锁来代替独占锁
- 当某个类第一次被加载时,JVM会通过解释字节码的方式来执行它。在某个时刻,如果一个方法运行的次数足够多,那么动态编译器会将它编译为机器代码,当编译完成后,代码的执行方式将从解释执行变为直接执行
- 当持有锁的时间相对较长,或者请求锁的平均时间间隔较长,那么应该使用公平锁。在这些情况下,”插队”带来的吞吐量提升则可能不会出现
- 在一些内置锁无法满足需求的情况下,ReentrantLock可以作为一种高级工具。当需要一些高级功能时才应该使用ReentrantLock,这些功能包括:可定时的、可轮询的与可中断的锁获取操作,公平队列,以及非块结构的锁。否则,还是应该优先使用synchronized
- 读/写锁:一个资源可以被多个读操作访问,或者被一个写操作访问,但两者不能同时进行
- 可以使用java语言和类库提供的底层机制来构造自己的同步机制,包括内置的条件队列、显示的Condition对象以及AbstractQueuedSynchronized框架
- 当使用条件等待时(Object.wait或Condition.await):a)通产都有一个条件谓词-包括一些对象状态的测试,线程在执行前必须首先通过这些测试b)在调用wait之前测试条件谓词,并且从wait中返回时再次进行测试c)在一个循环中调用wait d)确保使用与条件队列相关的锁来保护构成条件谓词的各个状态变量e)当调用wait、notify或notifyAll等方法时,一定要持有与条件队列相关的锁f)在检查条件谓词之后以及开始执行相应的操作之前,不要释放锁
- 活跃性故障:死锁、活锁、丢失的信号。丢失的信号:线程必须等待一个已经为真的条件,但在开始等待之前没有检查条件谓词
- 大多数情况下应该有限选择notifyAll而不是单个的notify。只有同时满足两个条件时,才能用单一的notify而不是notifyAll:a)所有等待线程的类型都相同,只有一个条件谓词与条件队列相关,并且每个线程在从wait返回后将执行相同的操作b)单进单出,在条件变量上的每次通知,最多只能唤醒一个线程来执行
- 对于每个依赖状态的操作,以及每个修改其他操作依赖状态的操作,都应该定义一个入口协议和出口协议,入口协议就是该操作的条件谓词,出口协议则包括:检查被该操作修改的所有状态变量,并确认它们是否使用某个其他的条件谓词变为真,如果是,则通知相关的条件队列
http://www.cnblogs.com/ikuman/p/4435235.html