Concurrent
1 Blockiing Queue(阻塞式队列)
2 ConcurrentMap(并发映射)
3 ExecutorService
4 Lock
1.1概述
1.1.1 满足队列FIFO的特点
1.1.2 阻塞式队列都是有界的,阻塞式队列的大小是固定的
1.1.3 如果队列已满,则添加元素的线程会被阻塞,直到队列中有元素被取出;如果队列为空,则获取元素的线程也会被阻塞,直到队列中被添加元素
1.1.4 阻塞式队列适合于生产消费模型
1.1.5 阻塞式队列中要求元素非空
1.2实现类
1.2.1 ArrayBlockingQueue - 阻塞式顺序队列
a. 底层是基于数组来进行存储
b. 在使用的时候需要指定容量,并且容量指定之后是不可变的
package cn.tedu.queue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(5);
// 添加元素
queue.add("a");
queue.add("b");
queue.add("c");
queue.add("d");
queue.add("e");
// 队列已满
// 抛出异常
// queue.add("f");
// 返回false
// boolean b = queue.offer("g");
// System.out.println(b);
// 产生阻塞
// queue.put("h");
// 定时阻塞
queue.offer("r", 5, TimeUnit.SECONDS);
System.out.println(queue);
}
}
1.2.2 LinkedBlockingQueue - 阻塞式链式队列
a. 底层是基于链表实现
b.在使用的时候可以指定容量也可以不指定。如果指定了容量,则容量固定;如果不指定容量,则容量默认为Integer.MAX_VALUE,即2^31-1。
package cn.tedu.queue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
public class BlockingQueueDemo2 {
public static void main(String[] args) throws InterruptedException {
LinkedBlockingQueue<String> queue = new LinkedBlockingQueue<>();
// 队列为空
// 抛出异常
// System.out.println(queue.remove());
// 返回null
// System.out.println(queue.poll());
// 产生阻塞
// System.out.println(queue.take());
// 定时阻塞
System.out.println(queue.poll(5, TimeUnit.SECONDS));
}
}
1.2.3 PriorityBlockingQueue - 具有优先级的阻塞式队列
a. 在使用的时候如果不指定容量,则默认容量为11
b.在遍历元素的时候是有序的,这也就要求添加的元素对应的类必须实现Comparable -
自然排序 - 如果是通过实现Comparable接口来实现的排序称之为是自然排序
c. 迭代遍历不保证排序
package cn.tedu.queue;
import java.util.concurrent.PriorityBlockingQueue;
public class PriorityBlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
// PriorityBlockingQueue<String> queue = new PriorityBlockingQueue<>(7);
//
// queue.put("g");
// queue.put("a");
// queue.put("r");
// queue.put("h");
// queue.put("d");
// queue.put("t");
// queue.put("u");
PriorityBlockingQueue<Student> queue = new PriorityBlockingQueue<>(5);
queue.put(new Student("曹洋", 80, 59));
queue.put(new Student("李帅", 30, 61));
queue.put(new Student("孟祥冰", 40, 19));
queue.put(new Student("肖旭伟", 40, 70));
queue.put(new Student("谷丰硕", 18, 30));
// for (int i = 0; i < 7; i++) {
// System.out.println(queue.take());
// }
for (Student s : queue) {
System.out.println(s);
}
}
}
class Student implements Comparable {
private String name;
private int age;
private int score;
public Student(String name, int age, int score) {
this.name = name;
this.age = age;
this.score = score;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", score=" + score + "]";
}
// 按照年龄进行升序排序
// this - o ===> 升序
// o - this ===> 降序
@Override
public int compareTo(Student o) {
return this.age - o.age;
}
}
4. SynchronousQueue - 同步队列
a. 在使用的时候不需要指定容量。默认容量是1并且只能为1
2.2 实现类
2.2.1 ConcurrentHashMap 底层是基于数组+链表存储,默认初始容量为16,默认加载因子是0.75,每次扩容默认增加一倍
2.2.2 在JDK1.8中,为了提高增删效率,ConcurrentHashMap引入了红黑树机制。当桶中的元素个数超过8个的时候,桶中的链表会扭转成一棵红黑树;如果红黑树的节点个数不足7个,那么红黑树会扭转回链表。ConcurrentHashMap中树化的条件是桶的个数>=64个
2.2.3红黑树特点:
i. 所有的节点的颜色非红即黑
ii. 根节点必须是黑节点
iii. 红节点的子节点必须是黑节点,但是反之不成立
iv. 最底层的叶子节点必须是黑的空节点
v. 从根节点到任意一个叶子节点途经的黑节点个数必须一致,即黑节点高度一致
vi. 新添节点的颜色一定是红色
2.2.4 红黑树修正:
i. 涂色:父子节点为红,并且叔父节点为红,那么需要将父节点以及叔父节点涂黑,
然后将祖父节点涂红
ii. 左旋:父子节点为红,叔父节点为黑,并且当前节点为右子叶,那么需要以当前节点为轴进行左转
iii. 右旋:父子节点为红,叔父节点为黑,并且当前当前节点为左子叶,那么需要以父节点为轴进行右旋
iv 红黑树的查询时间复杂度是O(logn)
2.2 ConcurrentNavigableMap - 并发导航映射
- 这个映射中提供了用于截取子映射的方法 - headMap/tailMap/subMap
- ConcurrentNavigableMap本身是一个接口,所以通常使用它的实现类
ConcurrentSkipListMap - 并发跳跃表映射 - 底层是基于了跳跃表来实现的 - 跳跃表:
a. 跳跃表要求元素必须有序
b. 跳跃表可以形成多层,但是最上层的跳跃表中元素个数要至少2个
跳跃表适合于查询多的场景
分区 02-Concurrent包 的第 6 页
c. 跳跃表适合于查询多的场景
d. 跳跃表本身是一个典型的以空间换时间的产物
e. 在跳跃表中如果新增节点,这个节点是否要提取到上层的跳跃表中,遵循"抛硬币"原则
f. 跳跃表的时间复杂度是O(logn)
package cn.tedu.map;
import java.util.concurrent.ConcurrentNavigableMap;
import java.util.concurrent.ConcurrentSkipListMap;
public class ConcurrentNavigableMapDemo {
public static void main(String[] args) {
// 创建映射
ConcurrentNavigableMap<String, Integer> map = new ConcurrentSkipListMap<>();
// 添加元素
map.put("g", 3);
map.put("a", 6);
map.put("s", 7);
map.put("y", 0);
map.put("e", 5);
map.put("w", 8);
System.out.println(map);
// 截取子映射
// 从头开始截取到指定的位置
System.out.println(map.headMap("e"));
// 从指定位置开始截取到尾部
System.out.println(map.tailMap("e"));
// 从指定位置开始截取到指定位置
System.out.println(map.subMap("e", "w"));
}
}
3.1ExecutorService 本质上是一个线程池,当线程池创建好之后,里面暂时没有任何线程,此时这个池子是空的
3.1.1 核心线程:核心线程在使用完成之后不会被销毁
3.1.2工作队列:所有的核心线程都被占用,放工作队列中
3.1.3临时线程:工作队列也被占满用临时队列(临时线程在处理完请求之后不会被立即销毁而是会等待指定的一段时间再销毁。而如果在指定的时间内又接受了新的请求,则临时线程会继续存活)
3.1.4RejectedExecutionHandle:如果临时线程被全部占用,再来的请求会交给RejectedExecutionHandler来进行拒绝
package cn.tedu.pool;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ExecutorServiceDemo {
public static void main(String[] args) {
// 创建一个执行器服务
// corePoolSize - 核心线程数量
// maximumPoolSize - 最大线程数量 = 核心线程数 + 临时线程数
// keepAliveTime - 临时线程存活时间
// unit - 时间单位
// workQueue - 工作队列
// handler - 拒绝执行处理器
ExecutorService es = new ThreadPoolExecutor(5, // 5个核心线程
10, // 5个核心+5个临时
5, TimeUnit.SECONDS, // 临时线程在使用完成之后要存活5s
new ArrayBlockingQueue<Runnable>(5), // 工作队列中能够存储5个请求
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("拒绝执行" + r);
}
});
// 把任务提交给线程池来执行
for (int i = 0; i < 18; i++)
es.execute(new ESRunnable());
// 关闭线程池
es.shutdown();
}
}
class ESRunnable implements Runnable {
@Override
public void run() {
try {
System.out.println("hello~~~");
Thread.sleep(3000);
System.out.println("finished~~~");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.2 Callable
- Callable是JDK1.5提供的一个定义线程的类
- Callable和Runnable的区别:
a. 返回值:Runnable没有返回值;Callable允许返回结果
b. 启动方式:Runnable可以通过Thread或者线程池来启动;Callable只能通过线程池启
动
c. 容错机制:Runnable不允许抛出异常,导致无法利用全局方式(例如Spring中的异常通
知)来处理异常;Callable允许抛出异常,可以利用全局方式来进行处理
package cn.tedu.pool;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class ExecutorServiceDemo2 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
// 特点:
// 1. 没有核心线程全部都是临时线程
// 2. 临时线程的数量是Integer.MAX_VALUE。考虑实际开发中
// 单台服务器能处理的线程数量是有限的
// 导致可以认为这个线程池能够处理无限多的请求
// 3. 临时线程的存活时间是1min
// 4. 工作队列是一个同步队列,只能存储1个元素
// 大池子小队列
// 适应场景:适用于短任务高并发场景
// ExecutorService es = Executors.newCachedThreadPool();
// 特点:
// 1. 没有临时线程全部都是核心线程
// 2. 工作队列是一个LinkedBlockingQueue
// 并且这个队列在创建的时候没有容量
// 那么意味着这个队列的容量是Integer.MAX_VALUE
// 可以认为这个队列的容量是无限的
// 也就意味着这个线程池可以存储无限多的任务
// 小池子大队列
// 适应场景:适用于长任务场景 - 百度云盘下载
ExecutorService es = Executors.newFixedThreadPool(5);
// Callable线程不能通过Thread启动,只能通过线程池启动
// submit既可以执行Runnable也可以执行Callable
Future<String> f = es.submit(new CallableDemo());
// 关闭线程池
es.shutdown();
// 从Future中将实际结果来解析出来
System.out.println(f.get());
}
}
// 泛型规定的是返回结果的类型
class CallableDemo implements Callable {
@Override
public String call() throws Exception {
return "SUCCESS";
}
}
3.3 ScheduledExecutorService - 定时执行器服务
这个类能提供定时的效果
package cn.tedu.pool;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorServiceDemo {
public static void main(String[] args) {
ScheduledExecutorService ses = Executors.newScheduledThreadPool(5);
// 这个方法能起到定时的效果
// command - 提交的任务
// delay - 延时时间
// unit - 时间单位
// 延迟5s再执行提交的任务
// ses.schedule(new ScheduledRunnable(), 5, TimeUnit.SECONDS);
// 每隔5s执行一次这个任务
// 从上一次的开始来计算下一次的启动时间
// 如果线程执行时间大于间隔时间
// 则两次任务的间隔时间以线程执行时间为准
ses.scheduleAtFixedRate(
new ScheduledRunnable(), 0,
5, TimeUnit.SECONDS);
// 从上一次的结束来计算下一次的启动时间
ses.scheduleWithFixedDelay(
new ScheduledRunnable(), 0,
5, TimeUnit.SECONDS);
}
}
class ScheduledRunnable implements Runnable {
@Override
public void run() {
try {
System.out.println("running~~~");
Thread.sleep(8000);
System.out.println("finished~~~");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
3.4ForkJoinPool - 分叉合并池
如果数据量较大,则适合使用分叉合并;如果数据量比较小,此时循环的效率反而更高
package cn.tedu.pool;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.Future;
import java.util.concurrent.RecursiveTask;
public class ForkJoinPoolDemo {
public static void main(String[] args) throws InterruptedException, ExecutionException {
long begin = System.currentTimeMillis();
// long sum = 0;
// for (long i = 0; i <= 100000000000L; i++) {
// sum += i;
// }
// System.out.println(sum);
// 创建分叉合并池
ForkJoinPool pool = new ForkJoinPool();
Future<Long> f = pool.submit(new Sum(1, 100000000000L));
System.out.println(f.get());
pool.shutdown();
long end = System.currentTimeMillis();
System.out.println(end - begin);
}
}
@SuppressWarnings(“serial”)
class Sum extends RecursiveTask {
private long start;
private long end;
public Sum(long start, long end) {
this.start = start;
this.end = end;
}
@Override
public Long compute() {
if (end - start <= 10000) {
long sum = 0;
for (long i = start; i <= end; i++) {
sum += i;
}
return sum;
} else {
long mid = (start + end) / 2;
Sum left = new Sum(start, mid);
Sum right = new Sum(mid + 1, end);
// 分叉
left.fork();
right.fork();
// 合并
return left.join() + right.join();
}
}
}
4 Lock
- 非静态同步方法的锁对象是this;静态同步方法的锁对象是当前类的字节码
- 在JDK1.5中,提供了Lock接口。通过lock方法加锁,通过unlock进行解锁
在非公平策略下,线程的执行次数并不均等甚至会出现比较大的差距;在公平策略
下,线程的执行次数应该是均等的
a.
b. 从效率角度考虑,非公平的效率会相对较高
c. synchronized使用的是非公平策略
d. Lock默认是非公平策略 - 锁的公平和非公平策略:
- ReadWriteLock - 读写锁:
ReadWriteLock l = new ReentrantReadWriteLock();
// 加读锁
l.readLock().lock();
// 解读锁
l.readLock().unlock();
package cn.tedu.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockDemo {
static int i = 0;
public static void main(String[] args) throws InterruptedException {
Lock l = new ReentrantLock();
new Thread(new Add(l)).start();
new Thread(new Add(l)).start();
Thread.sleep(2000);
System.out.println(i);
}
}
class Add implements Runnable {
private Lock l;
public Add(Lock l) {
this.l = l;
}
@Override
public void run() {
// 加锁
l.lock();
for (int i = 0; i < 10000; i++) {
LockDemo.i++;
}
// 解锁
l.unlock();
}
}
package cn.tedu.lock;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch cdl = new CountDownLatch(6);
new Thread(new Teacher(cdl)).start();
new Thread(new Student(cdl)).start();
new Thread(new Student(cdl)).start();
new Thread(new Student(cdl)).start();
new Thread(new Student(cdl)).start();
new Thread(new Student(cdl)).start();
// 在计数归零之前,需要让主函数所在线程先陷入阻塞
// 当计数归零之后,自动的放开阻塞
cdl.await();
System.out.println("开始考试~~~");
}
}
class Teacher implements Runnable {
private CountDownLatch cdl;
public Teacher(CountDownLatch cdl) {
this.cdl = cdl;
}
@Override
public void run() {
try {
Thread.sleep((long) (Math.random() * 10000));
System.out.println("考官到达考场~~~");
// 减少1个计数
cdl.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Student implements Runnable {
private CountDownLatch cdl;
public Student(CountDownLatch cdl) {
this.cdl = cdl;
}
@Override
public void run() {
try {
Thread.sleep((long) (Math.random() * 10000));
System.out.println("考生到达考场~~~");
cdl.countDown();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}