线程的深入理解
一、线程池
1. 创建池原因
问题
● 线程是宝贵的内存资源、单个线程约占1MB空间,过多分配易造成内存溢出。
● 频繁的创建及销毁线程会增加虚拟机回收频率、资源开销,造成程序性能下降。
线程池
● 线程容器,可设定线程分配的数量上限。
● 将预先创建的线程对象存入池中,并重用线程池中的线程对象。
● 避免频繁的创建和销毁。
原理
2. 创建线程池
常用的线程池接口
和类
(java.util.concurrent
):
Executor
:线程池的顶级接口,execute()
方法。ExecutorService
:线程池接口,可通过submit (Runnable task)
提交任务代码;``Executors
:工厂类:通过此类可以获得一个线程池。newFixedThreadPool (int nThreads)
:获取固定数量的线程池。参数:指定线程池中线程的数量。(常用)newCachedThreadPool()
:获得动态数量的线程池,如不够则创建新的,没有上限。(常用)
四种创建方式:
*
* 1.1 创建固定线程个数的线程池
* ExecutorService executorService = Executors.newFixedThreadPool(4);
*
* 1.2 创建缓存线程池,线程个数由任务个数而定
* ExecutorService executorService1 = Executors.newCachedThreadPool();
*
* 1.3 创建单线程线程池
* Executors.newSingleThreadExecutor();
*
* 1.4 创建调度线程池,调度指:按周期或定时执行
* Executors.newScheduledThreadPool(corePoolSize);
*
底层调用的方法都是ThreadPoolExecutor
类的构造方法,其源码如下:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
corePoolSize
—— 池中所保存的核心线程数,包括空闲线程。maximumPoolSize
—— 池中允许的最大线程数。keepAliveTime
—— 当线程数大于核心线程数时,超出核心的线程数的其他线程如果空间时间超过keepAliveTime
会被回收unit
——keepAliveTime
参数的时间单位(活跃时间的单位)。workQueue
—— 用于存储尚等待被执行的任务。threadFactory
—— 创建新线程时使用的工厂。handler
—— 由于超出线程范围和队列容量时提交的任务被阻塞时所使用的处理程序。
@Example:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo {
public static void main(String[] args) {
//创建线程池
ExecutorService execService = Executors.newFixedThreadPool(5);
//提交任务
for (int i = 0; i < 10; i++) {
execService.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getId() + ": " +Thread.currentThread().getName());
}
});
}
//关闭线程
execService.shutdown();
}
}
二、线程安全的集合
Collections 工具类:
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Random;
public class ArraylistCollectionsDemo {
public static void main(String[] args) {
//创建一个线程不安全的集合
ArrayList<String> arrayList = new ArrayList<>();
//使用Collections中的线程安全方法装换成线程安全的集合
List<String> syncList = Collections.synchronizedList(arrayList);
//创建线程
for (int k = 0; k < 20; k++) {
int temp = k;
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10; j++) {
syncList.add(Thread.currentThread().getName() + ": " +new Random().nextInt(500));
System.out.println(syncList);
}
}
}).start();
}
}
}
1. List集合 + Queue队列
(1)List集合
- CopyOnWriteArrayList
- 线程安全的
ArrayList
,加强读写分离 - 写有锁,读无锁,读写之间不阻塞,优于读写锁
- 写入时,先
copy
一个容器副本、再添加新元素,最后替换引用 - 使用方式与
ArrayList
相同 - 使用
addIfAbsent()
添加元素,会遍历数组,如存在元素,则不添加(扔掉副本)。
- 线程安全的
CopyOnWriteArrayList<String> copyWArrayList = new CopyOnWriteArrayList<>();
copyWArrayList.add(x);
package java.util.concurrent;
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();
}
}
(2)Queue接口(队列)
Collection
的子接口,表示队列 FIFO 先进先出
- 常用方法
- 抛出异常
boolean add(E e)
将指定的元素插入此队列(如果立即可行且不会违反容量限制),在成功时返回 true,如果当前没有可用的空间,则抛出IllegalStateException
E remove()
获得第一个元素并移除(如果队列没有元素时,则抛异常)E element()
获取,但是不移除此队列的头(如果队列没有元素时,则抛异常)
- 返回特殊值(推荐使用)
boolean offer(E e)
将指定的元素插入此队列(如果立即可行且不会违反容量限制),当使用有容量限制的队列时,此方法通常要优于add(E)
,后者可能无法插入元素,而只是抛出一个异常。E poll()
获取并移除此队列的头,如果此队列为空,则返回null
E peek()
获取但不移除此队列的头;如果此队列为空,则返回null
- 抛出异常
ConcurrentLinkedQueue
位置:java.util.concurrent.ConcurrentLinkedQueue<E>
- 线程安全、可高效读写的队列,高并发下性能最好的队列
- 无锁、
CAS
比较交换算法,修改的方法包含三个核心参数(V, E, N)
V
:要更新的变量、E
:预期值、N
:新值。- 只有当
V==E
时,V=N
;否则表示已被更新过,则取消当前操作。
public class ConcurrentLinkedQueueDemo{
public static void main(String[] args) {
Queue<String> queue = new ConcurrentLinkedQueue<String>();
queue .offer("Hel1o");//插入
queue. offer("World");//插入
queue. poll();//删除He1lo
queue. peek();//获得World
}
}
BlockingQueue接口(阻塞队列)
- Queue的子接口,阻塞的队列,增加了两个线程状态为无限期等待的方法
- 方法
void put(E e)
将指定元素插入此队列中,如果没有可用空间,则等待。.E take()
获取并移除此队列头部元素,如果没有可用元素,则等待。- 可用于解决生产生、消费者问题。
1)ArrayBlockingQueue
- 数组结构实现,有界队列,需手动设值
public class ArrayBlockingQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> abq = new ArrayBlockingQueue<String>(10);
}
}
2)LinkedBlockingQueue
- 链表结构实现,有界队列。 (默认上限
Integer.MAX_VALUE
)
public class LinkedBlockingQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> lbg = new LinkedBlockingQueue<String>();
}
}
2. Set集合
- CopyOnWriteArraySet
- 线程安全的
Set
,底层使用CopyOnWriteArrayList
实现。 - 使用
add()
方法添加元素- 底层是
CopyOnWriteArrayList
的addIfAbsent(e) ——> indexOf()
,达到元素不重复。 - 底层是
CopyOnWriteArrayList
的addIfAbsent(e) ——> addIfAbsent(e, snapshot);
,达到安全目的。
- 底层是
- 线程安全的
CopyOnWriteArraySet<String> copyWArraySet = new CopyOnWriteArraySet<>();
copyWArraySet.add(x);
package java.util.concurrent;
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;
if (indexOf(e, current, common, len) >= 0)
return false;
}
Object[] newElements = Arrays.copyOf(current, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
3. Map集合
- ConcurrentHashMap
- 初始容量默认为16段(
Segment
) ,使用分段锁设计。不对整个Map加锁,而是为每个Segment
加锁。 - 当多个对象存入同一个
Segment
时,才需要互斥。 - 最理想状态为16个对象分别存入16个
Segment
, 并行数量16。 - 使用方式与
HashMap
无异。
- 初始容量默认为16段(
ConcurrentHashMap<Integer,String> conHashMap = new ConcurrentHashMap<>();
put() 底层实现:
final V putVal(K key, V value, boolean onlyIfAbsent) {
if (key == null || value == null) throw new NullPointerException();
int hash = spread(key.hashCode());
int binCount = 0;
for (Node<K,V>[] tab = table;;) {
Node<K,V> f; int n, i, fh;
if (tab == null || (n = tab.length) == 0)
tab = initTable();
else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {
if (casTabAt(tab, i, null,
new Node<K,V>(hash, key, value, null)))
break; // no lock when adding to empty bin
}
else if ((fh = f.hash) == MOVED)
tab = helpTransfer(tab, f);
else {
V oldVal = null;
synchronized (f) {
if (tabAt(tab, i) == f) {
if (fh >= 0) {
binCount = 1;
for (Node<K,V> e = f;; ++binCount) {
K ek;
if (e.hash == hash &&
((ek = e.key) == key ||
(ek != null && key.equals(ek)))) {
oldVal = e.val;
if (!onlyIfAbsent)
e.val = value;
break;
}
Node<K,V> pred = e;
if ((e = e.next) == null) {
pred.next = new Node<K,V>(hash, key,
value, null);
break;
}
}
}
else if (f instanceof TreeBin) {
Node<K,V> p;
binCount = 2;
if ((p = ((TreeBin<K,V>)f).putTreeVal(hash, key,
value)) != null) {
oldVal = p.val;
if (!onlyIfAbsent)
p.val = value;
}
}
}
}
if (binCount != 0) {
if (binCount >= TREEIFY_THRESHOLD)
treeifyBin(tab, i);
if (oldVal != null)
return oldVal;
break;
}
}
}
addCount(1L, binCount);
return null;
}