1. 重入锁ReentrantLock
一个使用示例:
重入锁灵活性好于synchronied:开发人员可以显示指定何时加锁何时释放锁. 重入: 指的同一个线程可多次获得锁(lock.lock()),但在释放锁时也必须finally{}释放(unlock())相同次数,释放少的话相当于该线程还持有这个锁,别的线程仍然无法进入临界区.
完全可以替代synchronied关键字.(早期前者性能好,jdk6以后差距不大) . 高级功能:
a. 中断响应:可在等待锁的过程中,根据需求取消对锁的请求,无需再等待,可停止工作了.对于处理死锁有一定帮助. 可中断的请求锁:lock.lockInterruptibly() 释放锁:lock.isHeldByCurrentThread();lock.unlock().跑起来之后,其中一个线程可interrupt()后catch异常跳过对另外一个锁的请求, 并释放自己锁,解除死锁
b. 锁申请等待限时
tryLock()也可以不带参数,该情况下,当前线程会尝试获得锁,如果锁未被占用,则立即true,否则立即false. 这种方式不会等待,不会引起死锁. 一个节选实例:
c. 公平锁
输出显示线程t1和t2会交替获得锁
2. 重入锁好搭档:Condition
同wait和notity配合synchronied的作用效果大致相同. 这个是配合重入锁使用的.如下示例:
3. 控制多个线程同时访问: 信号量 Semaphore. 广义上它是对锁的扩展,不论是内部锁synchronized还是重入锁,一次都只允许一个线程访问临界区. 而信号量可以指定多个线程同时访问临界区. 构造信号量对象时,指定信号量的准入数,即同时能申请到几个许可,类似于重入锁,一申请一释放. 否则会发生信号量泄漏,最后导致所有线程均不可进行下去.
4.读写锁 ReadWriteLock 读写分离,两个读线程间是不该阻塞的
5. CountDownLatch
end= new CountDownLatch(5); 准备线程完成一个end.countDown()一下,直到down五次时, 工作线程的end.await()阻塞处才能继续执行.
6. CyclicBarrier
7. 线程阻塞工具类 LockSupport .park()可以在线程内任意位置让线程阻塞. 与Thread.suspend()相比,它弥补了resume()在前发生,导致线程无法继续执行的情况 ;; 和Object.wait()相比,他不需要先获得某个对象的锁,也不会抛出InterruptedException.
unpark函数为线程提供“许可(permit)”,线程调用park函数则等待“许可”。这个有点像信号量,但是这个“许可”是不能叠加的,“许可”是一次性的。
比如线程B连续调用了三次unpark函数,当线程A调用park函数就使用掉这个“许可”,如果线程A再次调用park,则进入等待状态。
注意,unpark函数可以先于park调用。比如线程B调用unpark函数,给线程A发了一个“许可”,那么当线程A调用park时,它发现已经有“许可”了,那么它会马上再继续运行。
unpark函数可以先于park调用,这个正是它们的灵活之处。
一个线程它有可能在别的线程unPark之前,或者之后,或者同时调用了park,那么因为park的特性,它可以不用担心自己的park的时序问题
考虑一下,两个线程同步,要如何处理?
在Java5里是用wait/notify/notifyAll来同步的。wait/notify机制有个很蛋疼的地方是,比如线程B要用notify通知线程A,那么线程B要确保线程A已经在wait调用上等待了,否则线程A可能永远都在等待。编程的时候就会很蛋疼。
另外,是调用notify,还是notifyAll?
notify只会唤醒一个线程,如果错误地有两个线程在同一个对象上wait等待,那么又悲剧了。为了安全起见,貌似只能调用notifyAll了。
park/unpark模型真正解耦了线程之间的同步,线程之间不再需要一个Object或者其它变量来存储状态,不再需要关心对方的状态
8. 线程池
固定线程池Executors.newFixedThreadPool() .....
计划任务 newScheduledThreadPool() 方法FixedRate是以上一个任务开始执行时间为起点,之后的period时间调度下一次任务(如果任务时间大于period,则会在上一个任务结束后立即调用下一次). 而FixDelay则是上一个任务结束后在经过delay后进行下次任务调度. 需要注意的是如果想让计划任务稳定持续执行,要注意做好异常处理,否则会半路终止.
线程池内部实现,以及自定义线程池根据 见P102
线程池超负载了 拒绝策略 P106
自定义线程池 以及 扩展线程池方法(执行前执行后,线程池shutdown) P110
使用线程池有时在执行任务时会吞掉异常的堆栈信息,我们可以使用: pools.execute(runable) 或者 Future fu = pools.submit(r) ; fu.get() 这两种方式可以得到部分堆栈信息. 但得不到出错任务在哪里提交的信息. 需要进一步自定义
9. Fork/Jion框架 ForkJoinPool接收 RecursiveTask(有返回值,线程池执行完毕后会返回这个任务,可用get()获得其结果,如果任务没结束,则主线程会在get处等待)和RecursiveAction 任务. 这两种任务支持fork()分解和join()等待. 使用forkjion时注意栈溢出(stackoverflowerror)
10. ConcurrentHashMap 高效的并发map,线程安全的hashmap
CopyOnWriteArrayList 线程安全arraylist,在读多写少时性能远好于vector. 和读写锁相对的只有读读之间不阻塞, 它只有写写之间才会加锁阻塞
ConcurrentLinkedQueue 高效并发队列,可看作线程安全的LinkedList(链表结构,增删快),但是个queue,没有index 内部实现全用cas P123
BlockingQueue 接口,用链表,数组方式实现,表示阻塞队列,适于作为数据共享的通道.解耦生产者消费者. 常用实现:
ArrayBlockingQueue: 适合做有界队列(毕竟数组动态扩展不方便,创建时需要确定初始容量) 向队列中压入元素: offer()和put(). 前者不阻塞,队列满的话直接返回false, put会一直等待直到队列中有空闲的位置. 从队列弹出元素: poll()和take(), 同前, 队列为空时前者直接返回null, take()会等待直到队列内有可用元素.
LinkedBlockingQueue: 适合无界或者边界值超大的队列,内部大小可动态增加,不会因为一上去就因为较大容量吃掉很多内存.
ConcurrentSkipListMap 跳表的实现. 多份分级的数据,一级比一级详细. 空间换时间的