类 ThreadLocal 的使用
工具类 ThreadLocal 代表一个线程局部变量,通过把数据放在 ThreadLocal 中就可以为每个线程创建一个该变量的副本,使每一个线程都可以独立地改变自己的副本,从而不会和其他线程的副本冲突,从而避免并发访问的线程安全问题。典型的例子就是在 Hibernate 中为 SessionFactory 定义了一个 ThreadLocal
ThreadLocal 类只提供了如下三个 public 方法:
- T get():返回此线程局部变量中当前线程副本的值。
- void remove():删除此线程局部变量中当前线程的值。
- void set(T value):设置此线程局部变量中当前线程的值。
类 InheritableThreadLocal 的使用
类 InheritableThreadLocal 可以让子线程从父线程中取得值。
包装线程不安全的集合
常用的 ArrayList、HashSet、TreeSet、HashMap、TreeMap 等集合都是线程不安全的,也即是说,当多个线程向这些集合存、取元素时,就可能会破坏这些集合的数据完整性。
可以使用 Collections 工具类提供的类方法把这些集合包装成线程安全的集合。Collections 有如下几个静态方法:
- public static
Collection :返回指定 collection 对应的线程安全的 collection。synchronizedCollection(Collection c) - public static
List :返回指定 List 对象对应的线程安全的 List 对象。synchronizedList(List list) - public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m):返回指定 Map 对象对应的线程安全的 Map 对象。
- public static
Set :返回指定 Set 对象对应的线程安全的 Set 对象。synchronizedSet(Set s) - public static
SortedSet :返回指定 SortedSet 对象对应的线程安全的 SortedSet 对象。synchronizedSortedSet(SortedSet s) - public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m):返回指定 SortedMap 对象对应的线程安全的 SortedMap 对象。
应该在创建集合类对象后立即对其进行包装成线程安全的集合。
Java 提供的线程安全的集合
java.util.concurrent 包下提供了大量支持高效并发访问的集合接口和实现类。大致可分为两类。
以 Concurrent 开头的集合类
如 ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、ConcurrentLinkedQueue、ConcurrentLinkedDeque。
这些代表了支持并发访问的集合类,它们可以支持多个线程并发写入访问,这些写入线程的所有操作都是线程安全的,但读取操作不必锁定。当多个线程共享访问一个公共集合时,ConcurrentLinkedQueue 是一个恰当的选择,其不允许使用 null 元素。ConcurrentHashMap 在默认下支持16个线程并发写入。
需要注意的是,java.util 包下的普通 Collection 集合对象在创建迭代器后当集合元素发生改变时,会引发 ConcurrentModificationException。但是 ConcurrentLinkedQueue 和 ConcurrentHashMap 支持多线程并发访问,所有当使用迭代器来遍历集合时,该迭代器可能不能反映出创建迭代器之后所作的修改,但程序不会抛出任何异常。
ConcurrentHashMap 更适合作为缓存实现类使用。
以 CopyOnWrite 开头的集合类
如 CopyOnWriteArrayList、CopyOnWriteArraySet。
CopyOnWriteArraySet 的底层封装了 CopyOnWriteArrayList,因此它的实现机制完全类似于 CopyOnWriteArrayList 集合。
对于 CopyOnWriteArrayList,它采用了复制底层数组的方式来实现写操作。当线程对 CopyOnWriteArrayList 集合执行读取操作时,线程将会直接读取集合本身,无须加锁与阻塞。当线程对 CopyOnWriteArrayList 集合执行写入操作时(包括调用 add()、remove()、set()),该集合会在底层复制一份新的数据,接下来又对新的数组执行写入操作。由于对 CopyOnWriteArrayList 集合的写入操作都是对数组的副本执行操作,因此它是线程安全的。
CopyOnWriteArrayList 适合用在读取操作远远大于写入操作的场景中,例如缓存。
定时器 Timer
Timer 类主要负责计划任务的功能,在指定的时间开始执行一个任务,它在内部使用多线程的方式进行处理。
Timer 类负责设置计划任务,但封装任务的类却是 TimerTask 抽象类,执行计划任务的代码要放入 TimerTask 的子类中。
要注意,创建一个 Timer 就是启动一个新的线程,可根据需要在 new Timer(true)
把它设置为守护线程。
如何实现指定时间执行任务
schedule(TimerTask task,Date time):该方法的作用是在指定的日期执行一次某一任务,若计划时间早于当前时间,则立即执行。
同一个 Timer 对象可以设定多个 TimerTask,并且 TimerTask 是以队列的方式一个一个被顺序性地执行,所以执行的时间有可能和预期的时间不一致。因为前面的任务消耗较长的时间导致后面的任务启动的时间也别延后。
如何实现按指定周期执行任务
schedule(TimerTask task,Date firstTime,long period):该方法的作用是在指定的日期之后按指定的间隔周期,无限循环地执行某一任务,若计划时间早于当前时间,则立即执行。
schedule(TimerTask task,long delay):该方法的作用是以执行 schedule(TimerTask task,long delay) 方法当前的时间为参考时间,在此时间基础上延迟指定的毫秒数后执行一次 TimerTask 任务。
schedule(TimerTask task,long delay,long period):该方法的作用是以执行 schedule(TimerTask task,long delay,long period) 方法当前的时间为参考时间,在此时间基础上延迟指定的毫秒数,再以某一间隔时间无限次数地执行某一任务。
TimerTask 类提供 cancel() 方法,用于将自身从任务队列中进行清除。
Timer 类也提供了 cancel() 方法,不同的是会将任务队列中的全部任务进行清空。
要注意的是,Timer 类的 cancel() 方法有时候并不一定会停止计划任务,而是会正常执行。原因是 Timer 类中的 cancel() 方法有时候并没有争抢到 queue 锁,则让 TimerTask 类中的任务正常执行。