Concurrent包
阻塞式队列-BlockingQueue
遵循先进先出原则,阻塞式队列在使用时需要指定界限
ArrayBlockingQueue-阻塞式顺序队列
底层是基于数组来进行存储,使用的时候需要指定一个容量, 容量在指定之后不可改变。— 生产-消费模型
LinkedBlockingQueue-阻塞式链式队列
底层是基于链表(节点)来进行数据的存储。在使用的时候可以指定初始容量,也可以不指定。如果指定了容量,就以指定的容量为准来进行存储;如果不指定容量,那么默认容量是Integer.MAX_VALUE -> 231 - 1。如果不指定容量,一般认为这个容量是无限的
PriorityBlockingQueue-具有优先级的阻塞式队列
如果不指定容量,默认容量是11。如果将元素一一取出,那么会对元素进行自然排序 — 要求存储的对象所对应的类必须实现Comparable, 重写compareTo方法, 将比较规则写到方法中;如果进行迭代遍历, 那么不保证排序
SynchronousQueue-同步队列
只允许存储1个元素
ConcurrentMap-并发映射
ConcurrentHashMap-并发哈希映射
异步式线程安全的映射,在jdk1.8之前,采用的分段锁(采用读写锁—读锁:允许多个线程同时读,但是不允许写;写锁:只允许一个线程写,但是不允许读)机制;从jdk1.8开始,采用CAS(Compare and swap)无锁算法 — 内存值V,旧的预期值A,新的预期值B — 先根据内存值V找到变量的实际值,然后将实际值和旧的预期值A进行比较,如果一致,则将新的预期值B赋值对应变量,并且将A改为B;如果实际值和A不相等,将现在的值赋值给A然后重新计算重新比较。从jdk1.8开始,当桶中的元素个数超过8个,会将桶中的链表扭转成一棵红黑树。如果桶中的元素个数不足7个,会将红黑树在扭转回链表。
补充:
HashMap:
底层依靠数组+链表存储的数据。默认初始容量是16,默认加载因子是0.75f,默认扩容每次增加一倍。本身是一个异步式线程不安全的映射。
HashTable:
同步式线程安全的映射 — 对外提供的方法都是同步方法。
红黑树:
自平衡二叉查找树 — 时间复杂度O(logn)
特征:
- 每一个节点非红即黑
- 根节点一定是黑色
- 所有的叶子节点一定是黑色的nil节点
- 红节点的子节点一定是黑节点
- 任意一条路径中的黑色节点个数一致
- 插入的节点一定是红色
修复:
- 当前节点为红,并且父节点且叔父节点为红,那么将父节点以及叔父节点涂黑,然后将祖父节点涂红
- 当前节点为红,并且父节点为红且叔父节点为黑,当前节点为右子叶,以当前节点为轴进行左旋
- 当前节点为红,并且父节点为红且叔父节点为黑,当前节点为左子叶,以父节点为轴进行右旋
ConcurrentNavigableMap-并发导航映射
本身是一个接口,所以更多的是使用实现类 - ConcurrentSkipListMap - 并发跳跃表映射
跳跃表:
为了提高查询效率所产生一种数据结构 — 跳跃表是典型的以空间换时间的产物
如果跳跃表中插入新的元素,新的元素是否往上提取遵循"抛硬币"原则 — 1/2原则 - 只要保证这个节点有一半的概率被提取就可以
跳跃表适合于大量查询而不增删的场景
锁
Lock
比synchronized更加的灵活
公平和非公平策略:
非公平策略是当线程释放锁之后依然可以再次抢占;
公平策略是当线程释放锁之后会到队列中让其他线程进行抢占
从效率上考虑,非公平的效率会高
package cn.tedu.concurrent.lock;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockDemo {
public static void main(String[] args) {
Product p = new Product();
// 默认是非公平的
// Lock l = new ReentrantLock();
// 表示将其设置为公平策略
Lock l = new ReentrantLock(true);
new Thread(new Producer(p, l)).start();
new Thread(new Consumer(p, l)).start();
}
}
class Product {
private int count;
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
class Producer implements Runnable {
private Product p;
private Lock l;
public Producer(Product p, Lock l) {
this.p = p;
this.l = l;
}
@Override
public void run() {
while (true) {
// 加锁
l.lock();
int count = (int) (Math.random() * 1000 + p.getCount());
p.setCount(count);
System.out.println("生产了" + count);
// 解锁
l.unlock();
}
}
}
class Consumer implements Runnable {
private Product p;
private Lock l;
public Consumer(Product p, Lock l) {
this.p = p;
this.l = l;
}
@Override
public void run() {
while (true) {
l.lock();
int count = (int) (p.getCount() * Math.random());
p.setCount(p.getCount() - count);
System.out.println("本次剩余了" + p.getCount());
l.unlock();
}
}
}
CountDownLatch-闭锁
线程递减锁 - 对线程进行计数,当计数归零的时候会放开阻塞让线程继续往下执行
package cn.tedu.concurrent.lock;
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch cdl = new CountDownLatch(5);
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();
// 表示让线程阻塞,直到计数归零的时候阻塞才能放开
cdl.await();
System.out.println("考试结束~~~");
}
}
class Student implements Runnable {
private CountDownLatch cdl;
public Student(CountDownLatch cdl) {
this.cdl = cdl;
}
@Override
public void run() {
System.out.println("学生来到考场,准备考试~~~");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("学生交卷离开考场");
// 计数-1
cdl.countDown();
}
}
class Teacher implements Runnable {
private CountDownLatch cdl;
public Teacher(CountDownLatch cdl) {
this.cdl = cdl;
}
@Override
public void run() {
System.out.println("老师来到考场,准备考试~~~");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("老师收卷离开了考场~~~");
cdl.countDown();
}
}
CyclicBarrier-栅栏
当所有的线程都到达了同一个点之后才继续往下执行
package cn.tedu.concurrent.lock;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cb = new CyclicBarrier(4);
new Thread(new Runner(cb), "1号").start();
new Thread(new Runner(cb), "2号").start();
new Thread(new Runner(cb), "3号").start();
new Thread(new Runner(cb), "4号").start();
}
}
class Runner implements Runnable {
private CyclicBarrier cb;
public Runner(CyclicBarrier cb) {
this.cb = cb;
}
public void run() {
try {
Thread.sleep((long) (10000 * Math.random()));
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "选手到了起跑线~~~");
try {
// 表示让当前线程陷入阻塞
// await每执行一次,就会自动减少一个计数
// 当计数减为0的时候,阻塞就会自动放开
// 也就意味着当所有的被计数的线程都到达这个点之后才会放开阻塞继续向下执行
cb.await();
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(name + "选手听到枪响,跑了出去~~~~");
}
}
Exchanger-交换机
用于交换两个线程之间的信息的
package cn.tedu.concurrent.lock;
import java.util.concurrent.Exchanger;
public class ExchangerDemo {
public static void main(String[] args) {
Exchanger<String> ex = new Exchanger<>();
new Thread(new Spy1(ex)).start();
new Thread(new Spy2(ex)).start();
}
}
class Spy1 implements Runnable {
// 泛型表示交换的信息类型
private Exchanger<String> ex;
public Spy1(Exchanger<String> ex) {
this.ex = ex;
}
@Override
public void run() {
String info = "天王盖地虎";
try {
// 要和第二个线程交换信息
// 获取到了交换过来的信息
String msg = ex.exchange(info);
System.out.println("间谍1收到了间谍2的信息:" + msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Spy2 implements Runnable {
private Exchanger<String> ex;
public Spy2(Exchanger<String> ex) {
this.ex = ex;
}
@Override
public void run() {
String info = "宝塔镇河妖";
try {
// 和第一个线程交换信息
// 换过来第一个线程的信息
String msg = ex.exchange(info);
System.out.println("间谍2收到了间谍1的信息:" + msg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Semaphore-信号量
用于计数 - 信号在被用完之后,后来的线程就会被阻塞,直到有信号被归还,被阻塞的线程才能取得信号继续执行
package cn.tedu.concurrent.lock;
import java.util.concurrent.Semaphore;
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore s = new Semaphore(10);
for (int i = 0; i < 15; i++) {
new Thread(new Restaurant(s)).start();
}
}
}
class Restaurant implements Runnable {
private Semaphore s;
public Restaurant(Semaphore s) {
this.s = s;
}
@Override
public void run() {
// 获取到了1个信号,使计数-1
try {
s.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("一位客人占用了一张桌子~~~");
try {
Thread.sleep((long) (10000 * Math.random()));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("一位客人起身离开,空出一张桌子~~~");
// 释放1个信号,使计数+1
s.release();
}
}
线程池
如果每一个请求对应一个线程,那么会导致线程大量的创建和销毁。减少线程的创建和销毁,希望能够重复使用已有的线程,有了线程池 — 存储线程的队列
特点:
- 线程池在创建的时候里面是没有线程的
- 当过来请求的时候,就会线程池中创建一个线程来处理这个请求。当请求处理完毕的时候,线程就会还回线程池,等待下一个请求
- 核心线程在线程池中需要限定数量
- 如果所有的核心线程都在使用,那么再来的请求就会放入工作队列中。工作队列是一个阻塞式队列。
- 如果所有的核心线程都被占用并且工作队列已满,那么会创建临时线程去处理新的请求
- 临时线程处理完请求之后并不是立即销毁,而是存活一段时间,如果过了这段时间依然没有新的请求,那么临时线程就被销毁
手动创建线程池:
package cn.tedu.concurrent.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 ThreadPoolDemo {
public static void main(String[] args) {
// 执行器服务
// corePoolSize - 核心线程数量
// maximumPoolSize - 线程池的容量 = 核心线程数量 + 临时线程数量
// keepAliveTime - 临时线程的存活事件
// unit - 时间单位
// workQueue - 工作队列
// handler - 拒绝执行助手
ExecutorService es = new ThreadPoolExecutor(5, 10, 3000, TimeUnit.MILLISECONDS,
new ArrayBlockingQueue<Runnable>(5), new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println(new Thread(r).getName() + "线程被拒绝~~~");
}
});
// 执行线程
for (int i = 0; i < 17; i++) {
es.execute(new RDemo());
}
// 关闭线程池
es.shutdown();
}
}
class RDemo implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + ":hello");
try {
Thread.sleep(8000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
java提供的线程池:
package cn.tedu.concurrent.pool;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolDemo2 {
public static void main(String[] args) {
// 获取缓存线程池
// 特点:
// 1. 没有核心线程,所有线程都是临时线程
// 2. 线程池的容量可以认为是无限的
// 3. 每一个临时线程存活时间都是1min - 存活时间不长
// 4. 工作队列是一个同步队列 - 只能存储一个元素
// 总结:
// 1. 大池子小队列
// 2. 理论上能够处理任意多的请求
// 3. 适合于短任务场景,例如:聊天
// ExecutorService es = Executors.newCachedThreadPool();
// 混合线程池
// 特点:
// 1. 需要指定核心线程数量
// 2. 只有核心线程,没有临时线程
// 3. 工作队列是阻塞式链式队列,没有指定容量
// 总结:
// 1. 小池子大队列
// 2. 理论上能够存储任意多的请求
// 3. 适合于长任务场景,例如:文件下载
ExecutorService es = Executors.newFixedThreadPool(5);
}
}
补充:
Runnable线程可以通过Thread来启动,也可以通过线程池来执行
Callable线程只能通过线程池来执行
线程池的execute方法只能执行Runnable线程 es.execute(command);
线程池的submit方法即可以执行Runnable也可以执行Callable,将结果封装为Future
Callable和Runnable的区别
- Runnable线程执行完成之后没有返回值;Callable需要定义返回值类型并且返回结果
- Runnable线程可以通过Thread启动执行,也可以通过线程池启动执行;Callable只能通过线程池来执行
- Runnable没有容错机制,一旦出现异常需要自己处理;Callable可以将异常抛出利用全局的方式来进行处理
ScheduledExecutorService-定时执行者服务
package cn.tedu.concurrent.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);
ses.shutdown();
}
}
class ScheduledRunnable implements Runnable {
@Override
public void run() {
System.out.println("hello");
}
}
package cn.tedu.concurrent.pool;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduledExecutorServiceDemo2 {
public static void main(String[] args) {
ScheduledExecutorService ses = Executors.newScheduledThreadPool(5);
// 每5秒钟循环执行一个线程
// command - 线程
// initialDelay - 初始延迟时间 - 当线程池启动以后,线程需要延迟多长时间才能执行
// period - 间隔时间
// unit - 时间单位
// 以上一次的起始时间开始计算
// 如果线程的执行时间超过了指定的间隔时间
// 那么第二次线程不会立即启动而是等上一个线程执行完成之后再启动执行
// ses.scheduleAtFixedRate(new RDemo2(), 0, 5, TimeUnit.SECONDS);
// delay - 表示线程上次完成之后需要间隔多长时间再执行下一次
// 以上一次的结束实际开始计算
ses.scheduleWithFixedDelay(new RDemo2(), 0, 5, TimeUnit.SECONDS);
// 凌晨1点对数据进行更新
// 获取当前时间 - 计算当前时间到凌晨一点的时间间隔
// 每执行一次这个方法,就在当前时间上加上1天
}
}
class RDemo2 implements Runnable {
@Override
public void run() {
System.out.println("hi");
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Fork and Join-分叉合并
将一个大任务不断的分成许多个小任务(分叉),小任务在执行完成之后再将结果进行汇总(合并)
任务在分叉之后会分发到CPU不同的核上去,然后利用核进行数据的处理。原来的for循环只是在一个核上来执行的代码,所以就导致分叉合并的效率要高于for循环
分叉合并采取了work-stealing(工作窃取)策略来实现线程的高效操作 -当一个核上的所有线程执行完成,就会随机挑一个核,从这个核的线程队列中偷取最后一个线程回来,然后执行。
package cn.tedu.concurrent.pool;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.RecursiveTask;
public class ForkJoinPoolDemo {
public static void main(String[] args) {
long start = System.currentTimeMillis();
ForkJoinPool fk = new ForkJoinPool();
Long sum = fk.invoke(new Calc(1, 100000000000L));
fk.shutdown();
System.out.println(sum);
// long sum = 0;
// for(long i = 1; i <= 100000000000L; i++)
// sum += i;
// System.out.println(sum);
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
// RecursiveTask和RecursiveAction都可以实现分叉合并
// RecursiveTask表示有返回结果
// RecursiveAction不需要返回结果
class Calc extends RecursiveTask<Long> {
private static final long serialVersionUID = 2145165687757218781L;
private long start;
private long end;
private static final long THREHOLD = 1000; // 阈值
public Calc(long start, long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
// 如果start-end之间的数字个数>阈值,需要继续进行分叉
// 如果start-end之前的数字个数<=阈值,进行求和
long count = end - start;
if (count <= THREHOLD) {
long sum = 0;
for (long i = start; i <= end; i++)
sum += i;
return sum;
} else {
long mid = (start + end) / 2;
Calc left = new Calc(start, mid);
Calc right = new Calc(mid + 1, end);
// 将这个线程分叉出去
left.fork();
right.fork();
return left.join() + right.join();
}
}
}
Atomic-原子性
volatile本身是一个关键字,保证属性在多线程场景下的原子性
volatile — 限定线程在执行过程中必须按照给定的指令顺序来执行
package cn.tedu.concurrent.atomic;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
public class AtomicDemo2 {
// jdk1.8之前是 volatile + 锁
// jdk1.8开始是 volatile + CAS
static AtomicInteger ai = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
CountDownLatch cdl = new CountDownLatch(2);
new Thread(new Add2(cdl)).start();
new Thread(new Add2(cdl)).start();
cdl.await();
System.out.println(ai.get());
}
}
class Add2 implements Runnable {
private CountDownLatch cdl;
public Add2(CountDownLatch cdl) {
this.cdl = cdl;
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
// AtomicDemo2.ai.addAndGet(1);
// 默认+1
AtomicDemo2.ai.incrementAndGet();
}
cdl.countDown();
}
}