Java 高级多线程
线程池
为什么需要线程池?
- 有非常多的任务需要多线程来完成,且每个线程不需要很长时间来运行,频繁的创建和销毁进程
- 频繁创建和销毁线程比较消耗性能。有了线程池就不需要创建更多的线程来完成任务,因为线程可以重用
线程池原理
线程池用于维护一个队列,队列中保存着处于等待(空闲)状态的线程,不用每次都创建新的线程
线程池API
常用的线程池的接口和类在包
java.util.concurrent下
Executors:通过此类获得一个线程池
| ⽅法名 | 描述 |
|---|---|
| newFixedThreadPool(int nThreads) | 获取固定数量的线程池。参数:指定线程池中线程的数量。 |
| newCachedThreadPool() | 获得动态数量的线程池,如不够则创建新的,⽆上限。 |
| newSingleThreadExecutor() | 创建单个线程的线程池,只有⼀个线程。 |
| newScheduledThreadPool() | 创建固定⼤⼩的线程池,可以延迟或定时执⾏任务。 |
Callable接口
- JDK5加入,与Runnable接口类似,实现之后代表一个线程任务
- Callable具有泛型返回值、可以声明异常
public interface Callable<V>{
public V call() throws Exception;
}
Callable接口的使用
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> callable = new Callable<>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
};
// 把Callable对象,转成可执行任务
FutureTask<Integer> future = new FutureTask<>(callable);
Thread thread = new Thread(future);
thread.start();
Integer sum = future.get();
System.out.println(sum);
}
public static void main(String[] args) throws Exception {
ExecutorService es = Executors.newFixedThreadPool(1);
Future<Integer> result = es.submit(() -> {
int sum = 0;
for (int i = 0; i <= 100; i++) {
sum += i;
}
return sum;
}
);
Integer sum = result.get();
System.out.println("1~100的和为:" + sum);
es.shutdown();
}
Runnable接口与Callable接口的区别
- Callable接口中call方法有返回值,Runnable接口中run方法没有返回值
- Callable接口中call方法有声明异常,Runnable接口中run方法没有异常
Future接口
- Future接口表示将要执行完任务的结果
- get()以阻塞形式等待Future中的异步处理结果(Call()的返回值)
Lock接口
- JDK5加入,与synchronized比较,显式定义,结构更灵活
- 提供更多实用性方法,功能更强大,性能更优越
常用方法
| ⽅法名 | 描述 |
|---|---|
| void lock() | 获取锁,如锁被占⽤,则等待。 |
| boolean tryLock() | 尝试获取锁(成功返回true。失败返回false,不阻塞)。 |
| void unlock() | 释放锁。 |
重入锁
ReentrantLock:Lock接口的实现类,与sychronized一样具有互斥锁功能
读写锁
ReentrantReadWriteLock:
- ⼀种⽀持⼀写多读的同步锁,读写分离,可分别分配读锁、写锁。
- ⽀持多次分配读锁,使多个读操作可以并发执⾏。
互斥规则:
- 写-写:互斥,阻塞。
- 读-写:互斥,读阻塞写、写阻塞读。
- 读-读:不互斥、不阻塞。
- 在读操作远远⾼于写操作的环境中,可在保障线程安全的情况下,提⾼运⾏效率。
创建读写锁
// 创建读写锁
private ReentrantReadWriteLock rrw = new ReentrantReadWriteLock();
// 读锁
private ReentrantReadWriteLock.ReadLock readLock = rrw.readLock();
// 写锁
private ReentrantReadWriteLock.WriteLock writeLock = rrw.writeLock();
使用读写锁
readLock.lock();
readLock.unlock();
writeLock.lock();
writeLock.unlock();
线程安全集合
Collections⼯具类中提供了多个可以获得线程安全集合的⽅法。
注:JDK1.2提供,接⼝统⼀、维护性⾼,但性能没有提升,均以synchonized实现。
| ⽅法名 |
|---|
| public static Collection synchronizedCollection(Collection c) |
| public static List synchronizedList(List list) |
| public static Set synchronizedSet(Set s) |
| public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) |
| public static SortedSet synchronizedSortedSet(SortedSet s) |
| public static <K,V> SortedMap<K,V> synchronizedSortedMap(SortedMap<K,V> m) |
方法使用
// 以synchronizedList为例
ArrayList<String> arrayList = new ArrayList<>();
List<String> list = Collections.synchronizedList(arrayList);
CopyOnWriteArrayList
- 线程安全的
ArrayList,加强版读写分离。 - 写有锁,读⽆锁,读写之间不阻塞,优于读写锁。
- 写⼊时,先
copy⼀个容器副本、再添加新元素,最后替换引⽤。 - 使⽤⽅式与
ArrayList⽆异。
使用
CopyOnWriteArrayList<String> strings = new CopyOnWriteArrayList<>();
CopyOnWriteArraySet
- 线程安全的Set,底层使⽤
CopyOnWriteArrayList实现。 - 唯⼀不同在于,使⽤addIfAbsent()添加元素,会遍历数组。
- 如存在元素,则不添加(扔掉副本)。
使用
CopyOnWriteArraySet<String> syncSet = new CopyOnWriteArraySet<>();
ConcurrentHashMap
- 初始容量默认为16段(Segment),使⽤分段锁设计。
- 不对整个Map加锁,⽽是为每个Segment加锁。
- 当多个对象存⼊同⼀个Segment时,才需要互斥。
- 最理想状态为16个对象分别存⼊16个Segment,并⾏数量16。
- 使⽤⽅式与HashMap⽆异。
Queue
Collection的⼦接⼝,表示队列FIFO(First In First Out)。
常用推荐方法
| ⽅法名 | 描述 |
|---|---|
| boolean offer(E e) | 顺序添加⼀个元素 (到达上限后,再添加则会返回false)。 |
| E poll() | 获得第⼀个元素并移除 (如果队列没有元素时,则返回null)。 |
| E peek() | 获得第⼀个元素但不移除 (如果队列没有元素时,则返回null)。 |
ConcurrentLinkedQueue
- 线程安全、可⾼效读写的队列,⾼并发下性能最好的队列。
- ⽆锁、CAS⽐较交换算法,修改的⽅法包含三个核⼼参数(V,E,N)。
- V:要更新的变量、E:预期值、N:新值。
- 只有当V==E时,V=N;否则表示已被更新过,则取消当前操作。
BolckingQueue
- Queue的⼦接⼝,阻塞的队列,增加了两个线程状态为⽆限期等待的⽅法。
- 可⽤于解决⽣产⽣、消费者问题。
常用方法
| ⽅法名 | 描述 |
|---|---|
| void put(E e) | 将指定元素插⼊此队列中,如果没有可⽤空间,则等待。 |
| E take() | 获取并移除此队列头部元素,如果没有可⽤元素,则等待。 |
ArrayBlockingQueue
- 数组结构实现,有界队列。
- ⼿⼯固定上限。
LinkedBlockingQueue
- 链表结构实现,⽆界队列。
- 默认上限Integer.MAX_VALUE。
- 使⽤⽅法和ArrayBlockingQueue相同。