整理自炼数成金
源码连接:
1. 各种同步控制工具的使用
1.1.ReentrantLock
1.1.1. 可重入 单线程可以重复进入,但要重复退出
1.1.2. 可中断 lockInterruptibly()
1.1.3. 可限时 超时不能获得锁,就返回false,不会永久等待构成死锁
1.1.4. 公平锁 先来先得 (相对不公平锁,线程调度的效率会差点)
[java] view plain copy
公平是有代价的。如果您需要公平,就必须付出代价,但是请不要把它作为您的默认选择。
1.1.5. 代码示例
[java] view plain copy
可以看到 Lock 和 synchronized 有一点明显的区别 —— lock 必须在 finally 块中释放。否则,如果受保护的代码将抛出异常,锁就有可能永远得不到释放!这一点区别看起来可能没什么,但是实际上,它极为重要。忘记在 finally 块中释放锁,可能会在程序中留下一个定时炸弹,当有一天炸弹爆炸时,您要花费很大力气才有找到源头在哪。而使用同步,JVM 将确保锁会获得自动释放。
1.2.Condition
1.2.1. 概述
类似于 Object.wait()和Object.notify()
与ReentrantLock结合使用
Lock 对象则充当绑定到这个锁的条件变量的工厂对象,与标准的 wait 和 notify 方法不同,对于指定的 Lock ,可以有不止一个条件变量与它关联。这样就简化了许多并发算法的开发。例如, 条件(Condition) 的 Javadoc 显示了一个有界缓冲区实现的示例,该示例使用了两个条件变量,“not full”和“not empty”,它比每个 lock 只用一个 wait 设置的实现方式可读性要好一些(而且更有效)。
1.2.2. 主要接口
[java] view plain copy
1.2.3. API详解
await()方法会使当前线程等待,同时释放当前锁,当其他线程中使用signal()时或者signalAll()方法时,线 程会重新获得锁并继续执行。或者当线程被中断时,也能跳出等待。这和Object.wait()方法很相似。
awaitUninterruptibly()方法与await()方法基本相同,但是它并不会再等待过程中响应中断。
singal()方法用于唤醒一个在等待中的线程。相对的singalAll()方法会唤醒所有在等待中的线程。这和Obej ct.notify()方法很类似。
1.2.4.代码示例
[java] view plain copy
该示例是自定义阻塞队列的实现,从代码可以看出,Object. notify ()会对该Object所有wait队列中随机唤醒一个,可能唤醒的不是我们想要的,例如读操作唤醒了读操作,所以可能会造成死锁。而Condition可以有多个,所以可以有目的的唤醒操作。例如只唤醒读操作或写操作。
1.3. Semaphore(信号量)
1.3.1. 概述 共享锁 运行多个线程同时临界区
1.3.2. 主要接口
[java] view plain copy
1.3.3.代码示例
[java] view plain copy
Java 并发库 的Semaphore 可以很轻松完成信号量控制,Semaphore可以控制某个资源可被同时访问的个数,通过 acquire() 获取一个许可,如果没有就等待,而 release() 释放一个许可。比如在Windows下可以设置共享文件的最大客户端访问个数。
1.4.ReadWriteLock
1.4.1. 概述
ReadWriteLock是JDK5中提供的读写分离锁
读写锁:分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!
1.4.2. 访问情况
读-读不互斥:读读之间不阻塞。
读-写互斥:读阻塞写,写也会阻塞读。
写-写互斥:写写阻塞。
1.4.3. 主要接口
[java] view plain copy
1.4.4. 代码示例
[java] view plain copy
代码中get方法可以多个线程同时访问,当put方法被调用时会等正在进行的get方法全部释放了读锁,然后争取到写锁。但是写锁只能1个线程同时拥有,读锁可以多个线程同时拥有。
1.5.CountDownLatch
1.5.1. 概述
倒数计时器
一种典型的场景就是火箭发射。在火箭发射前,为了保证万无一失,往往还要进行各项设备、仪器的检查。 只有等所有检查完毕后,引擎才能点火。这种场景就非常适合使用CountDownLatch。它可以使得点火线程 ,等待所有检查线程全部完工后,再执行主线程
1.5.2. 主要接口
[java] view plain copy
1.5.3. 示意图
1.5.4. 代码示例
[java] view plain copy
1.6.CyclicBarrier
1.6.1. 概述
Cyclic意为循环,也就是说这个计数器可以反复使用。比如,假设我们将计数器设置为10。那么凑齐第一批1 0个线程后,计数器就会归零,然后接着凑齐下一批10个线程
1.6.2. 主要接口
public CyclicBarrier(int parties, Runnable barrierAction)
barrierAction就是当计数器一次计数完成后,系统会执行的动作 await()
1.6.3. 示意图
1.6.4. 代码示例
[java] view plain copy
输出结果:
线程Thread-0正在写入数据...
线程Thread-3正在写入数据...
线程Thread-2正在写入数据...
线程Thread-1正在写入数据...
线程Thread-2写入数据完毕,等待其他线程写入完毕
线程Thread-0写入数据完毕,等待其他线程写入完毕
线程Thread-3写入数据完毕,等待其他线程写入完毕
线程Thread-1写入数据完毕,等待其他线程写入完毕
当前线程Thread-3
所有线程写入完毕,继续处理其他任务...
所有线程写入完毕,继续处理其他任务...
所有线程写入完毕,继续处理其他任务...
所有线程写入完毕,继续处理其他任务...
从上面输出结果可以看出,每个写入线程执行完写数据操作之后,就在等待其他线程写入操作完毕。
当所有线程线程写入操作完毕之后,所有线程就继续进行后续的操作了。
1.7. LockSupport
1.7.1. 概述
提供线程阻塞原语
1.7.2. 主要接口
LockSupport.park();
LockSupport.unpark(t1);
1.7.3. 与suspend()比较
不容易引起线程冻结
1.7.4. 中断响应
能够响应中断,但不抛出异常。 中断响应的结果是,park()函数的返回,可以从Thread.interrupted()得到中断标志
1.8.ReentrantLock 的实现
1.8.1. CAS状态
1.8.2. 等待队列
1.8.3. park()
2. 并发容器
2.1.集合包装
为集合类包了一层衣服,为每个方法添加了同步的实现。
2.1.1. HashMap
Collections.synchronizedMap
public static Map m=Collections.synchronizedMap(new HashMap());
2.1.2. List
synchronizedList
2.1.3. Set
synchronizedSet
2.2.ConcurrentHashMap
高性能HashMap
看起来是一个Map数据结构,内部实现将其划分为几个小的Map,而且每个Map都有独立的锁,形成分区锁。每个分区的读写操作不影响另一个分区。
2.3.BlockingQueue
阻塞队列