java.concurrent包常见类详解

一、线程安全的集合类

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);  
              //与之前得到的数组状态之前比较,可能有其他线程修改0-common-1中的元素
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(); } }
 
  

3、ConcurrentHashMap

锁分离技术,多个segment段来处理并发操作,在此不多叙述,请看我的另一篇博客,会有详解:

链接:”ConcurrentHashMap源码解析

 
  

二、线程池

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, 优先级)执行。
(3)ExecutorService可以理解为程序员提供了一堆操作Executor的API。ExecutorService扩展了一些生命周期管理的办法,一个Executor的生命周期有三种状态:运行、关闭、终止。一个ExecutorService实例就是一个线程池。
举个例子:
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 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 {  
    private int id;  
  
    public TaskWithResult(int id) {  
        this.id = id;  
    }  
  
    @Override  
    public String call() throws Exception {  
        return "返回结果为:"+id;  
    }  
}  

四、locks.ReetrantLock 可重入锁

五、BlockingQueue

阻塞队列

你可能感兴趣的:(并发多线程)