一、并发简介
1.操作系统会为各个独立执行的进程分配各种资源,包括内存、文件句柄以及安全证书等。
2.多个程序同时执行的优点:提高资源利用率;公平性(不同的进程都有机会执行);便利性;
3.线程是轻量级进程,它是基本的调度单位,同一个进程的所有线程共享进程的内存地址单元。
4.线程带来的问题:安全性问题(是否能获取到预期的值)、活跃性问题(某件正确的事情最终会
发生)、性能问题(响应时间、吞吐量、资源消耗)。
5.RMI远程方法调用:RMI是代码能够调用在其它JVM运行的对象。当通过RMI调用某个远程方法时,
传递给方法的参数必须被打包到一个字节流中,通过网络传输给远程JVM,然后由远程JVM拆包并
传给远程方法。
二、线程安全性
1.线程安全:当多个线程访问某个类时,这个类始终表现出正确的行为,就称这个类是线程安全的。
2.线程安全类中封装了必要的同步机制,所以客户端不需要进一步采取同步措施。
3.无状态的对象(既不包含任何域,也不包含任何对其它类中域的引用)一定是线程安全的,大多数
Servlet是无状态的。
4.内置锁Synchronized:线程在进入同步代码块之前会自动获得锁,并在退出同步代码块时自动释放锁。
5.锁重入:当某个线程请求一个由其它线程持有的锁时,发出请求的线程就会阻塞。内置锁是可重入的时,
就是线程可以获得一个已经由它自己持有的锁。重入提升了加锁行为的封装性。
6.由于锁能使其保护的代码路径以串行形式来访问,可以通过锁来构造一些协议来实现对共享状态的独占访问,
只要始终遵循这些协议,就能确保状态的一致性。
7.一种常见的加锁约定:将所有的可变状态都封装在对象内部,并通过对象的内置锁对所有访问可变状态的代码
进行同步,使得在该对象上不会发生并发访问。
三、对象的共享
1.为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制。
2.在没有同步的情况下,编译器、处理器在运行时都可能对操作的执行顺序进行一些意想不到的调整。
3.java内存模型的要求:变量的读取操作和写入操作都必须是原子操作。
4.volatile是更轻量级的同步机制,使用volatile修饰的变量是线程安全的,但自增、自减时会失效。
5.发布一个对象:使对象能够在当前作用域之外的代码中使用。
6.逸出:不该被发布的对象被发布了。不要在构造过程中使this引用逸出。
7.线程封闭:线程安全的一种方式,就是把访问的变量作为局部变量封闭起来。栈封闭是线程封闭的特例。
8.ThreadLocal类:能使线程中的某个值与保存值的对象关联起来。提供了get和set方法,为每个使用该变量的
线程都存有一份独立的副本。
9.不可变对象:对象在创建之后就不能被修改。不可变对象一定是线程安全的。
10.Final域:能确保初始化过程的安全性。
11.将可变对象安全发布的模式。
四、对象的组合
1.将现有的一些线程安全的组件组合成更大规模的组件或程序。
2.设计线程安全的类:收集同步需求、实例封闭、线程安全性委托、添加功能等等。
五、同步容器类
1.同步容器类:包括Vector、Hashtable。实现方式:将他们的状态封装起来,并对每个公有方法都进行同步,
使得每次只有一个线程能访问容器的状态。同步容器类是线程安全的。
2.如果在对Vector进行迭代时,另一个线程删除了一个元素,并且这两个操作交替执行时,就会
抛出ArrayIndexOutOfBoundsException异常。
3.在并发过程中,如果容器在迭代过程中被修改时,就会发生ConcurrentModificationException。
4.解决以上异常的方法:在迭代期间进行加锁或者“克隆”容器,在副本上进行迭代。
5.BlockingQueue扩展了Queue,增加了可阻塞的插入和获取等操作。如果队列为空,那么获取元素的操作将
一直阻塞,直到队列中出现一个可用的元素。如果队列已满,那么插入元素的操作将一直阻塞,直到出现可用的空间,采用了“生产者-消费者”模式。
6.ConcurrentHashMap是线程安全,它并没有将每个方法都加上同步锁,而是使用了分段锁实现。
7.对象池利用了串行线程封闭,将对象“借给”一个请求线程。
8.线程阻塞或暂停原因:等待I/O操作结束、等待获取一个锁、等待从sleep方法中醒来、等待另一个线程计算结果等。
9.中断是一种协作机制,中断另一个线程时,若这个线程不能被中断,则会出现InterrupedException异常。
10.闭锁:是一种同步工具类,可以延迟线程的进度直到其达到终止状态。闭锁可以用来确保某些活动直到其它活动都
完成后才继续执行。CountDownLatch是一种灵活的闭锁实现,它可以使一个或多个线程等待一组事件发生。闭锁状态包括一个技术器,表示需要等待的事件数量。
11.FutureTask也可做闭锁,在Executor框架中表示异步任务。Callable表示的任务可以抛出受检查或未受检查的异常。
12.计算信号量用来控制同时访问某个特定资源的操作数量或者同时执行某个指定操作的数量。Semaphore可以用于实现资源池。
13.栅栏:它能阻塞一组线程直到某个事件发生。栅栏与闭锁的关键区别在于,所有的线程必须到达栅栏位置,才能继续执行。
实现有:CyclicBarrier,Exchanger等。
六、任务执行
1.在线程中执行任务:串行地执行任务;显示地为任务创建线程(如:new Thread(task).start());无限制创建线程(
为每个任务分配一个线程:缺点:线程生命周期开销非常高、活跃的线程会消耗系统资源、影响稳定性)。
2.Executor用于执行任务的框架,基于生产者-消费者模式,提交任务的操作相当于操作者,执行任务的线程相当于消费者。
3.线程池:用来管理一组线程的资源池,通过重用现有的线程而不是创建新线程,可以在处理多个请求时分摊线程创建和
销毁过程中产生的巨大开销。通过适当的调整线程池的大小,可以创建足够多的线程以便使处理器保持忙碌状态,同时还可以防止过多线程相互竞争资源而使应用程序耗尽内存而失败。
4.通过Executors静态方法创建线程池:newFixedThreadedPool、newSingleThreadExecutor等。
5.Executor的生命周期的3种状态:运行、关闭、和已终止。
6.Timer负责管理延迟任务。
7.Runnable和Callable描述的都是抽象的计算任务,Runnable是一个有很大的局限的抽象,因为它不能返回一个值或抛出
一个受检查的异常。
七、任务的取消与关闭
1.要使任务和线程能安全、快速、可靠地停下来并不容易。java中并没有提供任何机制来安全地终止线程,只提供了中断
这种协作机制,能够让一个线程终止另一个线程的当前工作。
2.任务取消的原因:用户请求取消、有时间限制的操作、错误、程序或服务关闭等。
3.中断:调用interrupt并不意味立即停止目标线程正在进行的工作,而只是传递了请求中断的消息。
4.合理的中断策略:尽快退出,在必要时进行清理,通知某个所有者该线程已经退出。由于每个线程拥有各自的中断策略,
所以除非知道中断对该线程的含义,否则就不应该中断这个线程。
5.可通过Future中的cancel方法取消任务或线程。
6.处理不可中断的阻塞,可通过其它方式达到效果。
7.ExecutorService提供了shutdown和shutdownNow方法可用于停止基于线程的服务。
8.JVM既可以正常关闭,也可强行关闭。在正常关闭中,JVM首先调用所有已注册的关闭钩子。关闭钩子是指通过
Runtime.addShutdownHook注册的但尚未开始的线程。JVM并不能保证关闭钩子的调用顺序。
9.线程分为普通线程和守护线程。在JVM启动时创建的所有线程中,除了主线程外,其余都是守护线程,新线程将继承
创建它的线程的守护状态。
10.避免使用终结器,finalize方法。
八、线程池使用
1.线程池的理想大小取决于被提交事务的类型以及所部署系统的特性。避免设置过大、过小的情况。
2.ThreadPoolExecutor是一个灵活的、稳定的线程池、允许进行各种定制。
3.创建线程池:ThreadPoolExecutor executor = new ThreadPoolExecutor(...);
九、避免活跃性危险
1.一般的数据库会检测死锁并能从死锁中恢复。若出现死锁,会选择放弃一个事务,使其它事务继续执行。JVM出现死锁时
可能造成应用程序完全停止,或者特定的子系统停止,或者性能降低。死锁很少立即显示出来,并不是每次都会发生死锁,可能会在高负载的情况下出现。
2.出现死锁的情况:锁顺序死锁、动态锁顺序死锁、协作对象之间发生死锁、资源死锁等。
3.其它活跃性危险:
饥饿:线程无法访问它所需的资源而不能继续执行。活锁:不会阻塞线程,但也不能继续执行,因为线程将不断重复执行相同的操作,而且总会失败。
十、性能与可伸缩性
1.线程最主要的目的是提高程序的运行性能,用更小的资源做更多的事情。
2.可伸缩性指的是:当增加计算资源时(如CPU、内存、存储容量或I/O带宽),程序的吞吐量或者处理能力能相应的增加。
3.多个线程的调度和协调过程都需要一定的性能开销。
4.上下文切换:当可运行的线程数大于CPU的数量时,操作系统会将某个正在运行的线程调度出来,从而使其它线程能够
使用CPU,这就形成了一次上下文切换。
5.非竞争的同步可以在JVM中进行处理,竞争的同步可能需要操作系统的介入,从而增加开销。
6.自旋等待:通过循环不断地尝试获取锁直到成功。
7.减少锁竞争:缩小锁的范围、减小锁的粒度、锁分段等。
十一、显示锁
1.Lock与ReentrantLock:
Lock lock = new ReentrantLock();lock.lock();.... lock.unlock();
2.显示锁提供了一些扩展功能,能更加灵活,并且对队列有更好的控制,但ReentrantLock不能完全代替synchronized,
只有在synchronized无法满足需求时,才应该使用它。
3.volatile变量是更轻量级的同步机制,使用这些变量时不会发生上下文切换或线程调度等操作。
4.硬件对并发的支持:比较并交换CAS、非阻塞的计数器。
5.原子变量类比锁的粒度更细,量级更轻。