以前整理了一些关于Java虚拟机的内容,而做为Java程序员,并发编程也是很重要的方面,下面就根据《Java并发编程实战》这本书作一些整理。程序员
线程安全性:当多个线程访问某个类时,这个类始终都能表现出正确的行为,那么就称这个类是线程安全的。编程
咱们不只但愿防止某个线程正在使用对象状态而另外一个线程在同时修改该状态;而且但愿确保当一个线程修改了对象状态后,其余线程可以看到发生的状态变化。数组
加锁与可见性:加锁的含义不只仅是局限于互斥行为,还包括内存可见性。缓存
用来确保将变量的更新操做通知到其余线程。编译器和运行时,会注意到这个变量是共享的,所以不会将该变量上的操做与其余内存操做一块儿重排序。不会执行加锁操做,是一种比synchronized关键字更轻量级的同步机制。可是它不提供原子性。安全
当下面全部条件知足时,才应该使用volatile变量:并发
1.对变量的写入操做不依赖变量的当前值,或者你能确保只有单个线程更新变量的值。框架
2.该变量不会与其余状态变量一块儿归入不变性条件中异步
3.在访问变量时不须要加锁工具
一种避免同步的方式就是不共享数据。若是仅在单线程内访问数据,就不须要同步,这种技术被称为线程封闭。在Swing以及JDBC connection中大量使用了线程封闭技术。性能
栈封闭:只能经过局部变量才能访问对象。而局部变量位于执行线程的栈中。缺点:维护困难,容易使对象逸出。
TheadLocal类:它使线程中的某个值与保存值的对象关联起来。当某个频繁执行的操做须要一个临时对象,而同时但愿避免在每次执行时都从新分配该临时对象,就可使用这项技术。这些特定于线程的值保存在Thread对象中,当线程终止后,这些值会做为垃圾回收。Threadlocal变量相似于全局变量,它能下降代码的可重用性,并在类之间引入隐含的耦合性,所以在使用时要格外当心。
不可变对象必定是线程安全的。
当知足如下条件时,对象才是不可变的:
1.对象建立后,其状态就不能修改
2.对象的全部域都是final类型
3.对象时正确建立的(在对象的建立期间,this引用没有逸出)
好比有Vector和Hashtable。它们实现线程安全的方式是:把它们的状态封装起来,并对每一个公有方法都进行同步,使得每次只有一个线程能访问容器的状态。
及时失败策略(fail-fast):当它们发现容器在迭代过程当中被修改时,就会抛出一个ConcurrentModificationException
加锁是一种方案,但开销大;克隆也是一种方案,但容器过大时,开销也不小。
加锁有时也避免不了隐藏迭代器的状况。
Java5提供了多种并发容器来改进同步容器的性能。
采用了分段锁机制。包含16个锁的数组,每一个锁保护散列桶的1/16.
返回的迭代器具备弱一致性,这意味着它能够容忍并发的修改,当建立迭代器时会遍历已有的元素,并能够(可是不保证)在迭代器被构造后将修改操做反映给容器。
为了反映容器的并发特性,size和isEmpty这些方法的语言被略微减弱了,由于它们返回的只是一个估计值,结果有可能已通过期了。但因为返回值一直在变化,这些方法在并发环境下用处很小。咱们会更关注get、put、containKey和remove等操做。
劣势:得到全部锁的开销更大,好比resize的时候
要确保锁上的竞争频率高于在锁保护的数据上发生竞争的频率。当每一个操做都请求多个变量时,锁的粒度将很难下降。性能与可伸缩性的权衡。
每一个分段都维护一个独立的计数器,调用size,返回值缓存到一个volatile变量,容器被修改则为-1,正值返回,负值须要从新计算。
用于替代同步List
BlockingQueue
能够延迟线程的进度直到其到达终止状态。
CountDownLatch,初始化为一个正数,表示须要等待的事情数量。countDown方法递减计数器,表示有一个事情已经发生了,而await方法等待计数器达到零。
FutureTask能够作闭锁,它在Executor框架中表示异步任务,还能够用来表示一些时间较长的计算。
信号量:Counting Semaphore用来控制同时访问某个特定资源的操做数量。经过acquire和release来获取和释放信号量。
栅栏:与闭锁的区别在于,全部线程必须同时到达栅栏位置,才能继续执行。闭锁用于等待时间,而栅栏用于等待其余线程。await()会阻塞到全部线程都到达栅栏的位置。若是栅栏打开,则全部线程被释放;而且每一个线程会拿到await返回的一个惟一的到达索引号,能够利用索引号选出一个领导线程,并在下一次迭代中由改领导线程执行一些特殊的工做。
无限制建立线程的不足
1.线程生命周期的开销很是高
2.资源消耗:过多的线程,会产生大量空闲线程占用内存,给垃圾回收带来压力;线程竞争CPU资源也会带来其余的性能开销。
3.稳定性:实际上,可建立线程的数量上存在一个限制。
它为异步任务执行框架提供了基础,该框架能支持多种不一样类型的任务执行策略;另外,它的实现还提供了对生命周期的支持,以及统计信息收集、应用程序管理机制和性能监视等机制。
基于生产者-消费者模式,提交任务的操做至关于生产者(生成待完成的工做单元),执行任务的线程则至关于消费者(执行完这些工做单元)。将任务提交和任务执行解藕了。
好处:
1.分摊在线程建立和销毁过程当中产生的巨大开销
2.当请求到达时,工做线程一般已经存在,所以不会有延迟,提升了响应性
3.保持处理器合理忙碌,避免线程过分竞争资源
经过Executors中的静态工厂方法来建立线程池:
1.newFixedThreadPool(int num):建立固定长度的线程池,满了之后规模再也不变化。
2.newCachedThreadPool:建立一个可缓存的线程池,适合那些短任务;若是已有线程都在忙,会增长新线程;超过60s未工做的线程会被回收。
3.newSingleThreadExecutor:单线程,确保任务的顺序(FIFO、LIFO等)。
4.newScheduledThreadPool(int num):建立一个固定长度的线程池,并且是以延迟或定时的方式来执行任务。
ExecutorService扩展了Executor接口,添加了一些用于生命周期管理的方法,同时还有一些用于任务提交的便利方法。
生命周期的三种状态:运行,关闭,已终止。
shutdown方法采起平缓关闭:再也不接受新任务,等待已提交任务完成(包括还未开始执行的任务)。
shutdownNow方法采起粗暴的关闭:尝试取消全部运行的任务,再也不启动队列中的未执行的任务。
Callable被认为是主入口点将返回一个值,并可能抛出一个异常。描述的是一种抽象的计算任务。
Future表示一个任务的生命周期,并提供响应的方法来判断是否已经完成或取消,以及获取任务的结果和取消任务等。
ExecutorService中全部submit方法都将返回一个Future。将Runnable或Callable提交给Executor,并获得一个Future用来得到任务的执行结果或取消任务。
还能够显示地为某个指定地Runnable或Callable实例化一个FutureTask。
一般,中断是实现取消的最合理方式。
经过Future来实现取消。
线程池的理想大小取决于被提交任务的类型以及所部署系统的特性。
若是线程池过大,那么大量的线程将在相对不多的CPU和内存资源上发生竞争。
若是线程池太小,那么不少处理器没有被利用起来,从而下降了吞吐率。
用来实现Executors里的一些工厂方法:
public ThreadPoolExecutor( int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockinQueue<Ruannable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {...}
线程池的基本大小、最大大小和存活时间等因素共同负责线程的建立与销毁。
基本大小就是线程池的目标大小,即在没有任务执行时线程池的大小,只有工做队列满了才会建立超出这个数量的线程。
线程池的最大大小表示可同时活动的线程数量的上限。
若是某个线程的空闲时间超过了存活时间,那么将被标记为可回收的,而且当线程池的当前大小超过了基本大小时,这个线程将被终止。
ThreadPoolExecutor容许提供一个BlockingQueue来保存等待执行的任务。基本的任务排队方法有3种:无界队列、有界队列和同步移交。
newFixedThreadPool和newSingleThreadExecutor在默认状况下将使用一个无界的LinkedBlockingQueue。线程都忙碌,在队列中等候,若是任务持续到达,并超过了线程池处理它们的速度,那么队列将无限制地增长。
一种更稳妥的资源管理策略是使用有界队列,例如ArrayBlockingQueue、有界的LinkedBlockingQueue、PriorityBlockingQueue。有界队列有助于避免资源耗尽的状况发生。须要队列的大小和线程池大小配合。
对于很是大的或者无界的线程池,能够经过SynchronousQueue来避免任务排队。它不是一个真正的队列,而是一种在线程之间进行移交的机制。要将一个元素放入SynchronousQueue中,必须有另外一个线程正在等待接受这个元素。若是没有线程正在等待,而且线程池的当前大小小于最大值,那么将建立一个新的线程。不然根据饱和策略,这个任务将被拒绝。newCachedThreadPool就使用了这种机制。
只有当任务相互独立时,为线程池或工做队列设置界限才是合理的。
饱和策略
当有界队列被填满后,饱和策略开始发挥做用。能够经过setRejectedExecutionHandler来修改。不一样的饱和策略:AbortPolicy,CallerRunsPolicy,DiscardPolicy,DiscardOldestPolicy。
AbortPolicy:默认的策略,抛出异常,本身写代码来处理
DiscardPolicy:会悄悄抛弃该任务
DiscardOldestPolicy:抛弃下一个将被执行的任务,而后尝试从新提交新任务。不要与优先队列一块儿使用。
CallerRunsPolicy:将某些任务回退到调用者,从而下降新任务的流量。它不会在线程池的某个线程中执行新提交的任务,而是在一个调用了execute的线程中执行该任务。
线程工厂
每当线程池须要建立一个线程时,都是经过线程工厂方法来完成的。ThreadFactory中只定义了一个方法newThread,每当线程池须要建立一个新线程时都会调用这个方法。许多状况下都须要使用定制的线程工厂方法。
在子类中改写方法beforeExecute,afterExecute和terminated。
若是每次至多只能得到一个锁,那么就不会产生锁顺序死锁。
显示使用Lock类中的定时tryLock功能。内置锁,没有得到锁会一直等下去。
可伸缩性指的是增长资源时,程序的吞吐量或处理能力相应地增长。
Amdahl定律描述:在增长计算资源地状况下,程序在理论上可以实现最高加速比,这个值取决于程序中可并行组件与串行组件所占比重。
1.减小锁地持有时间
2.减小锁地粒度
3.下降锁地请求频率
4.使用带有协调机制的独占锁,这些机制容许更高的并行性。
可重入的加锁语义,灵活性。
轮询锁与定时锁:tryLock方法。
轮询锁,若是不能得到所须要的锁,那么可使用可轮询的锁获取方式,从而尝试得到控制权。它会释放已经得到的锁,而后从新尝试获取全部锁。
定时锁,若是操做不能在指定的时间内给出结果,那么就是使程序提早结束。
可中断的锁获取操做。
公平性。
显示的Condition对象。