Java并发编程实践 笔记(节选)

1.Executor:
1).为什么会有Executor? 无限的创建线程会导致浪费很多的资源,线程频繁的挂起与唤醒都会影响性能。Executor就充当了线程池的一部分。
2).常见的线程池是Executors.newFixedThreadPool .它可以生产一个定长的线程池,当线程池中的线程不够用的时候就到生产一个线程直到达到额定的线程数。这时候线程的长度就不会再改变。还有例如newCacheThreadPool可以根据当前使用情况,回收空闲线程。还有例如:ScheduledThreadPool,这个线程池可以用来代替Timer。Timer有如下两个问题:一.如果执行耗时的任务,有可能出现同一个时间点应该执行两个或以上的任务,这时候其中一个或多个任务可能会丢失,或者被重复执行。二:如果上一个任务抛了运行时异常以后,后面的任务就丢失了。ScheduledThreadPool多线程定时执行,可解决以上两个问题
3).ExecutorService还能联合Future使用,使用Executor.submit(Callable xx) 然后返回一个Future 。

2.Future:
1).为什么要有Future?以往一个线程执行一个任务之后就无法知道该任务的状态,运行情况,更加不能停止该任务,例如这个任务已经不需要执行了,我们还不能把它停掉,白白浪费性能。Future提供查询任务的状态,是否完成,是否已取消,并且可以取消执行任务。并且额外还提供阻塞返回和定时阻塞返回。
2).典型的实现是FutureTask

3.CompleteService
1.为什么要用CompleteService?使用executorService.submit返回一个Future之后,可以一直循环判断Future是否完成,然后拿结果。更好的办法是使用CompleteService. CompleteService可以把已完成的Future放到一个BlockingQueue中,然后我们只管从中取Future就可以了。

4.BlockingQueue
1).为什么要使用BlockingQueue?一般的Queue如果take不到值就返回null.但是有时候我们想要他等待有结果的时候给我们返回。
2).BlockingQueue内部使用条件队列来实现。

5.HashTable和ConcurrentHashMap的一个重要区别于锁的粒度上面。HashTable使用内部锁保护了整一个容器而ConcurrentHashMap中的就采用了分离锁,分离出了16个锁,每个锁守护容器的一部分。分离锁的一个负面的效果是,如果想对容器独占锁,那么获取所有的锁,例如ConcurrentHashMap需要扩容,重排的,reHash的时候。

6.闭锁(Latch)与关卡(Barrier):一个闭锁工作起来就像一个大门,直到闭锁达到终点之前,门一直关着,没有线程能通过,在终点状态到来的时候,门开了,允许所有线程都通过。闭锁是一次性的使用的对象,一旦进入到最终状态,就不能被重置了。关卡(barrier)类似于闭锁。它们都能够阻塞一组线程,直到某个条件达到,闭锁等待的是事件,而关卡等待的是其他线程。其实闭锁和关卡都可以理解为一个计数器。

7.你可以将Thread Local<T> 看做map<Thread,T> 它存储了与线程相关的值,但是,事实上它并非是这样实现的,与线程相关的值存储在线程对象自身中,线程终止后,这些值会被垃圾回收

8.如何避免锁循环死锁:.如果多个线程用固定的秩序来获得锁就能避免“锁顺序死锁”问题。使用System.identityHashCode比较两个对象,根据比较的结果来固定访问锁的秩序,从而可以避免两个对象互相等待对方的锁的现象。

9.与单线程相比,使用多线程总会引入一些性能的开销,这些开销包括:与协调线程相关的开销(加锁,信号,内存同步),增加上下文切换,线程的创建和消亡,以及调度的开销。当线程被过度使用后,这些开销甚至会超过提高后的吞吐量响应性和计算能力带来的补偿。所以使用多线程的时候要注意减少这些开销。

10.Amdahl定律告诉我们,程序的可伸缩性是由串行执行的代码的比例决定的,(当处理器的个数为理想最大化的时候公式为1/F ,F为串行化比例)而串行代码大部分是由独占锁的,所以要提高可伸缩性,可以用以下方式:减少锁的竞争,减少锁的粒度,减少锁的持有时间,使用非独占锁(读写分离锁等)。

11.对象池对于并发应用来说不是一个好的选择,并发应用中多使用线程本地分配的块来取消共用对象堆中的同步,如果引入对象池,必然会涉及到对象池同步的问题,这便产生了线程阻塞的问题,其代价比直接分配的代价要大。

12.ReentrantLock 和Synchronized 有相同的作用,提供互斥锁和内存可见性。只有当需要使用到ReentrantLock的高级功能的时候才应该使用ReentrantLock否则使用Synchronized。高级功能包括:可定时的,可轮询的与可中断的获取锁操作,公平队列,或者非块结构的锁。内部锁相比显式锁的优势在于:内部锁使用起来更加简洁,并且使用ReentrantLock的时候,如果忘记在finally块中调用unlock,导致锁没有释放,可能会引起大问题,所以如果没有使用到ReentrantLock的高级功能应该使用Synchronized

13.ReentrantLock和Semaphore他们的实现都用到了同一个基类,AbstractQueueSynchronizer(AQS);其他还有CountDownLatch,ReentrantWriteReadLock,SynchronousQueue和FutureTask都扩展了这个类。AQS,它管理一个关于状态信息的单一整数,状态信息可以通过protected类型的getState,setState和compareAndSetState等方法进行操作。ReentrantLock用来表示拥有他的线程已经请求了多少次锁,Semaphore用它来表现剩余的许可数,FutureTask用它来表示任务状态(未开始,执行,完成,取消)。

14.什么时候使用内部条件队列,什么时候使用外部的条件队列?这个和ReentrantLock和Synchronized之间的选择是一样的,如果需要用到条件队列的一些高级特性的,例如,需要用到公平队列,或者让每个锁对应多个等待集。

15.java.util.concurrent 包中的许多类,比使用synchronized有更好的性能和伸缩性,提升的原始来源是:原子变量和非阻塞的同步机制。
java中加锁的语法比较简洁但是jvm和os管理锁的工作并不简单,加锁需要遍历JVM中整个复杂的代码路径,并可能引起系统级的加锁,线程挂起以及上下文切换。CAS(compare and swap)的性能更佳
1).CAS最重要的缺点是:他强迫调用者处理竞争(通过重试,回退,或者放弃);然而如果是使用锁,在锁被获得之前,却可以通过阻塞自动处理竞争。
2).即使是在“快速路径”(无竞争锁的路径)上,获取和释放无竞争锁的开销大约也是CAS的两倍。p324
非阻塞算法使用了低层级并发原语,比如比较并交换,取代了锁。原子变量类向用户提供了这些低层级原语,也能够当作‘更佳的volatile变量’使用。同时提供了整数类和对象引用的原子化更新操作。

16.重排序:JMM(java memory model)允许从不同的角度观看一个动作,此时该动作会以不同的次序执行,这种错序执行的现象称为重排序。JMM为所有程序内部的动作定义了一个关系,叫做happens-before。要想保证执行动作B的线程看到动作A的结果(无论A和B是否发生在同一个线程中),A和B之间就必须满足happens-before关系。如果两个操作之间未依照happens-before关系排序,JVM可以对它们随意排序。


 

你可能感兴趣的:(java,thread,多线程,编程)