对于如何新建线程,join,yield,setDaemon,线程状态等这些简单知识就不作多介绍了,下面分享下咱们平常开发中 经常使用的一些知识,多线程这块的知识不少,后续会慢慢完善,若有错误和不足,还请各位大佬指出。java
好处:减少频繁建立线程带来的巨大的性能开销,同时设定了上线,能够防止建立线程数量过多致使系统崩溃的现象 先来看下java为咱们提供的四种已经建立好的线程池:数组
怎么使用?缓存
调用execute(runnable)或者submit(runnable)便可,两者有什么区别? sumit能够返回一个Future来获取执行的结果安全
上面四种线程池的构造函数以下:bash
/**
* @param corePoolSize 核心线程数量, 超出数量的线程会进入阻塞队列
* @param maximumPoolSize 最大可建立线程数量
* @param keepAliveTime 线程存活时间
* @param unit 存活时间的单位
* @param workQueue 线程溢出后的阻塞队列
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new
LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new Executors.FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new
LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new
SynchronousQueue<Runnable>());
}
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, TimeUnit.NANOSECONDS, new
ScheduledThreadPoolExecutor.DelayedWorkQueue());
}
复制代码
上面的4种线程池已经知足咱们平常开发中绝大部分的场景,可是有他们本身的弊端:多线程
FixedThreadPool和SingleThreadPoolPool : 请求队列使用的是LinkedBlockingQueue,容许的请求队列的长度是Integer.MAX_VALUE,可能会堆积大量的请求,从而致使 OOM.ide
CachedThreadPool和ScheduledThreadPool : 容许建立的线程数量是Integer.MAX_VALUE,可能会建立大量的线程池, 从而致使 OOM.函数
以上也就是阿里建议咱们本身配置线程池的缘由,那么线程池的那些参数究竟该如何配置? 线程池大小的配置: 通常根据任务类型 和 CPU的核数(N)工具
CPU密集型的任务多的话,须要减小线程的数量,这样能够下降切换线程带来的开销,建议配置线程池的大小为: N+1 IO密集型的任务到的话,须要增长线程的数量,建议配置为 N*2post
咱们来看一下AsyncTask的线程池配置:
private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
private static final int CORE_POOL_SIZE = Math.max(2, Math.min(CPU_COUNT - 1, 4));
private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
private static final int KEEP_ALIVE_SECONDS = 30;
private static final ThreadFactory sThreadFactory = new ThreadFactory() {
private final AtomicInteger mCount = new AtomicInteger(1);
public Thread newThread(Runnable r) {
return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
}
};
private static final BlockingQueue<Runnable> sPoolWorkQueue =
new LinkedBlockingQueue<Runnable>(128);
public static final Executor THREAD_POOL_EXECUTOR;
static {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_SECONDS, TimeUnit.SECONDS,
sPoolWorkQueue, sThreadFactory);
threadPoolExecutor.allowCoreThreadTimeOut(true);
THREAD_POOL_EXECUTOR = threadPoolExecutor;
}
复制代码
从AsyncTask的源码中咱们得知,核心线程的数量是至少有2线程,最多有4个线程,最好状况是有CPU的核心数-1,这样能够避免CPU在 后台工做的时候达到饱和。最大线程数量设置的是CPU_COUNT * 2 + 1,使用的阻塞队列是LinkedBlockingQueue,默认大小是 Integer.MAX_VALUE,可是AsyncTask使用时指定了其大小为128,下面说下阻塞队列:
一、ArrayBlockingQueue
是一个基于数组结构的有界阻塞队列,此队列按 FIFO(先进先出)原则对元素进行排序。
二、LinkedBlockingQueue
一个基于链表结构的阻塞队列,此队列按FIFO (先进先出) 排序元素,吞吐量一般要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()和newSingleThreadExecutor使用了这个队列
三、SynchronousQueue
一个不存储元素的阻塞队列。每一个插入操做必须等到另外一个线程调用移除操做,不然插入操做一直处于阻塞状态,吞吐量一般要高于LinkedBlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列。
四、PriorityBlockingQueue
一个具备优先级的无限阻塞队列。 五、DelayedWorkQueue 任务是按照目标执行时间进行排序。
阅读LiveData源码的时候发现有这个API,看名字就是到该怎么使用,这里就不做多介绍了,下面看代码
ArchTaskExecutor.getIOThreadExecutor().execute(new Runnable() {
@Override
public void run() {
}
});
ArchTaskExecutor.getMainThreadExecutor().execute(new Runnable() {
@Override
public void run() {
}
});
ArchTaskExecutor.getInstance().executeOnDiskIO(new Runnable() {
@Override
public void run() {
}
});
ArchTaskExecutor.getInstance().postToMainThread(new Runnable() {
@Override
public void run() {
}
});
复制代码
ArrayList是非线程安全的,在多线程中,对ArrayList进行迭代的时候,会报ConcurrentModificationException错误, 为了保证同步,咱们可使用集合工具类来对ArrayList进行包装,即Collections.synchronizedList。同时咱们也知道 Vector也是线程安全的,那么这二者有什么区别呢? Vector底层是数组实现的,使用同步方法来实现线程安全,Collections.synchronizedList并未修改list的底层实现,只是 对其进行了一层包装,使用同步代码块来实现线程安全。同步方法的锁是做用在方法上,因此锁的粒度会比同步代码块的大,同步代码的 执行效率是与锁的粒度成反比的,因此Collections.synchronizedList的执行效率是高于Vector,并且,Vector的全部对集合 的操做都加上了synchronized关键字,效率较低,已不推荐使用。 可是Collections.synchronizedList真的就线程安全了吗?
List<Integer> integers = Collections.synchronizedList(new ArrayList<Integer>());
Iterator<Integer> iterator = integers.iterator();
while (iterator.hasNext()){
doSth(iterator.next());
}
复制代码
屡次运行后咱们会发现依然会报ConcurrentModificationException错误,不是已是线程安全了吗,为何呢? 那有同窗可能会说是否是迭代器的问题,我换成for-Each来遍历,for-Each只是简洁,数组索引的边界值只计算一次罢了, 试一下,一样会报ConcurrentModificationException的错误。那么究竟为何? Collections.synchronizedList虽然每一个操做已经使用静态代码块,可是在迭代的时候,整个的迭代过程不是原子性的, 想要结果正确,咱们应该对整个迭代过程进行加锁处理:
List<Integer> integers = Collections.synchronizedList(new ArrayList<Integer>());
synchronized (integers){
Iterator<Integer> iterator = integers.iterator();
while (iterator.hasNext()){
doSth(iterator.next());
}
}
复制代码
看到上面的代码,咱们不由感到恐怖如斯,遍历一遍数据咱们都要加锁,那不是很慢嘛,因而,CopyOnWriteArrayList登场
咱们看下JUC(java.util.concurrent)包下面的线程安全的类是怎样替代老一代的线程安全的类的: Hashtable -> ConcurrentHashMap Vector -> CopyOnWriteArrayList 其实他们之间最大的不一样就是加锁粒度的不一样,新一代的容器的加锁粒度更小,好比ConcurrentHashMap用了cas锁、volatile等方式来实现线程安全。 注:JUC下面的安全容器在遍历的时候不会抛出ConcurrentModificationException的异常的。 那么下面咱们来学习下CopyOnWriteArrayList: 先来看下COW: 若是有多个调用者同时请求相同的资源的时候,他们会获取到相同的指针而且指向相同的资源,知道某一个调用者试图修改这个资源的时候,系统才会真正复制一份副本给到这个调用者,而其余的调用者看到的依然是以前的资源。 经过上面的概念咱们就能够得出一个结论了:在读操做比较多的状况下,使用COW效率更高。
CopyOnWriteArrayList#add()在添加的时候就上锁,并复制一个新数组,增长操做在新数组上完成,将array指向到新数组中,最后解锁 写加锁,读不加锁 问题:为何在遍历的时候,不用调用者显式的加锁呢? CopyOnWriteArrayList在使用迭代器遍历的时候,操做的都是原数组! 缺点: 1.若是CopyOnWriteArrayList常常要增删改里面的数据,常常要执行add()、set()、remove()的话,那是比较耗费内存的 2.CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性
跟同步机制相似,都是为了解决多线程访问同一变量形成的访问冲突问题,不一样的的是: 同步机制是经过锁机制牺牲时间来达到多线程安全访问的目的; ThreadLocal为每一个线程提供独立的变量副本,牺牲空间保证每一个线程访问相同变量时能够获得本身专属的变量,达到多线程安全访问的目的。
对于过可见性、有序性及原子性问题,一般状况下咱们能够经过Synchronized关键字来解决这些个问题,不过若是对Synchronized原理有了解的话,应该知道Synchronized是一个比较重量级的操做,对系统的性能有比较大的影响,因此,若是有其余解决方案,咱们一般都避免使用Synchronized来解决问题。而volatile关键字就是Java中提供的另外一种解决可见性和有序性问题的方案。对于原子性,须要强调一点,也是你们容易误解的一点:对volatile变量的单次读/写操做能够保证原子性的,如long和double类型变量,可是并不能保证i++这种操做的原子性,由于本质上i++是读、写两次操做