一、线程安全的集合类
1、CopyOnWriteArrayList
ArrayList的一个线程安全的实体。其中所有可变操作都是通过对底层数组经常一次新的复制来实现的。
比如add(E)时,容器自动copy一次出来然后在尾部添加。看源码:
public boolean add(E e) {
final ReentrantLock lock = this.lock; // 获取独占锁
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);// 重新生成一个新的数组实例,并将原始数组的元素拷贝到新数组中
newElements[len] = e; // 添加新的元素到新数组的末尾
setArray(newElements); // 更新底层数组
return true;
} finally {
lock.unlock();
}
}
可以看出,先加锁,复制一份数组,然后在尾部添加。
下面举出remove源码:
public E remove(int index) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
E oldValue = get(elements, index); // 获取volatile数组中指定索引处的元素值
int numMoved = len - index - 1;
if (numMoved == 0) // 如果被删除的是最后一个元素,则直接通过Arrays.copyOf()进行处理,而不需要新建数组
setArray(Arrays.copyOf(elements, len - 1));
else {
Object[] newElements = new Object[len - 1];
System.arraycopy(elements, 0, newElements, 0, index); // 拷贝删除元素前半部分数据到新数组中
System.arraycopy(elements, index + 1, newElements, index, numMoved);// 拷贝删除元素后半部分数据到新数组中
setArray(newElements); // 更新volatile数组
}
return oldValue;
} finally {
lock.unlock();
}
}
总结:
(1)在副本上面执行加锁操作,获取独占锁,防止其他线程同时修改数据,不会出现ConcurrentModificationException异常。 ArrayList中在多线程下执行修改会报这个异常。
(2) 底层数组Object[] array用变量volatile修饰,当数组更新时,其他线程能得到最新的值。
(3) CopyOnWriteArrayList适合在读多写少的并发应用中。比如缓存。因为写操作时,执行加锁,然后对整个list的copy操作相当耗时。
(4)迭代器支持hasNext(), next()等不可变操作,但不支持可变 remove()等 操作。
(5)使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照。
2、CopyOnWriteArraySet
(1)继承AbstractSet类,实现Set接口,元素不可以重复,它的底层结构时采用动态的数组。
(2)包含CopyOnWriteArrayList对象(final)。线程安全机制也是通过volatile和互斥锁实现的。
(3)添加操作时,是根据addIfAbsent()和addAllAbsent()这两个添加元素的API来判断元素是否存在,再决定是否添加。
(4)当判断元素不在其中时(这个操作未加锁,真正执行添加操作时原数组可能已改变或者添加的数已经在其中),则还需进行一次最新的数组复制,继续进行判断。
请看添加元素源码:
public boolean add(E e) {
return al.addIfAbsent(e); //al是CopyOnWriteArraySet里面定义的CopyOnWriteArrayList
}
public boolean addIfAbsent(E e) {
Object[] snapshot = getArray();//数组快照
return indexOf(e, snapshot, 0, snapshot.length) >= 0 ? false :
addIfAbsent(e, snapshot);
}
private static int indexOf(Object o, Object[] elements,
int index, int fence) { //判断对象在数组中的位置
if (o == null) {
for (int i = index; i < fence; i++)
if (elements[i] == null)
return i;
} else {
for (int i = index; i < fence; i++)
if (o.equals(elements[i]))
return i;
}
return -1;
}
private boolean addIfAbsent(E e, Object[] snapshot) { //添加元素不存在,执行添加操作 final ReentrantLock lock = this.lock; //获取抢占锁 lock.lock(); try { Object[] current = getArray(); //取得当前最新的数组 int len = current.length; if (snapshot != current) { //如果当前最新的数组和之前得到的数组快照不一样 // Optimize for lost race to another addXXX operation int common = Math.min(snapshot.length, len);
for (int i = 0; i < common; i++) if (current[i] != snapshot[i] && eq(e, current[i])) return false; //两个数组处于相同位置的元素比较和添加元素e已经在当前数组中,则不添加 if (indexOf(e, current, common, len) >= 0)//添加元素e在common~len-1中,不添加 return false; } Object[] newElements = Arrays.copyOf(current, len + 1);// 数组拷贝 newElements[len] = e; //添加尾部 setArray(newElements); //更新底层数组 return true; } finally { lock.unlock(); } }//与之前得到的数组状态之前比较,可能有其他线程修改0-common-1中的元素
3、ConcurrentHashMap
锁分离技术,多个segment段来处理并发操作,在此不多叙述,请看我的另一篇博客,会有详解:
二、线程池
Executor/Executors/Executor Service
Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。
(1)Executor:属于public类型的接口,用于提交,管理和执行Runnable任务,Executor.execute(Runnable)。
(2)Executors:提供一系列工厂方法用于创建线程池,返回的线程都实现了ExecutorService接口:Java通过Executors提供四种线程池,分别为:
- newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
- newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
- newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
- newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
举个例子:
ExecutorService executorService = Executors.newFixedThreadPool(10); executorService.execute(new Runnable() { public void run() { System.out.println("Asynchronous task"); } }); executorService.shutdown();
三、Callable和Future
Callable接口代表一个有返回值的操作。与Runnable有所不同:
(1)Callable规定的方法是call(),而Runnable规定的方法是run()。
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值。
(3)call()方法可抛出异常,而run()方法是不能抛出异常的。
(4)运行Callable任务可拿到一个Future对象,Future表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果.通过Future对象可了解任务执行情况,可取消任务的执行,还可获取任务执行的结果。
举个例子:
import java.util.concurrent.*; public class CallableTest { public static void main(String[] args) throws InterruptedException, ExecutionException{ ExecutorService es = Executors.newCachedThreadPool(); //创建线程池 Future<String> fs = es.submit(new TaskWithResult(1)); //提交callable任务 //Thread.sleep(1000); 1.线程未完成,打印"Future result is not yet complete" Thread.sleep(1000);//2.线程已完成,取得对应的String值 if(fs.isDone()){ System.out.println(fs.get()); }else { System.out.println("Future result is not yet complete"); } } } class TaskWithResult implements Callable<String> { private int id; public TaskWithResult(int id) { this.id = id; } @Override public String call() throws Exception { return "返回结果为:"+id; } }
四、locks.ReetrantLock 可重入锁
五、BlockingQueue
阻塞队列