Concurrent
一、阻塞式队列 - BlockingQueue
遵循先进先出(FIFO)的原则。阻塞式队列本身使用的时候是需要指定界限。
1.ArrayBlockingQueue - 阻塞式顺序队列 - 底层是基于数组来进行存储,使用的时候需要指定一个容量, 容量在指定之后不可改变。— 生产-消费模型
2.LinkedBlockingQueue - 阻塞式链式队列 - 底层是基于链表(节点)来进行数据的存储。在使用的时候可以指定初始容量,也可以不指定。如果指定了容量,就以指定的容量为准来进行存储;如果不指定容量,那么默认容量是Integer.MAX_VALUE -> 231 - 1。如果不指定容量,一般认为这个容量是无限的
3.PriorityBlockingQueue - 具有优先级的阻塞式队列 - 如果不指定容量,默认容量是11。如果将元素一 一取出,那么会对元素进行自然排序 — 要求存储的对象所对应的类必须实现Comparable, 重写compareTo方法, 将比较规则写到方法中;如果进行迭代遍历, 那么不保证排序
4.SynchronousQueue - 同步队列 - 只允许存储1个元素
二、并发映射 - ConcurrentMap
1.HashMap - 底层依靠数组+链表存储的数据。默认初始容量是16,默认加载因子是0.75f,默认扩容每次增加一倍。本身是一个异步式线程不安全的映射
2.Hashtable - 同步式线程安全的映射 — 对外提供的方法都是同步方法
3.ConcurrentHashMap - 异步式线程安全的映射 - 在jdk1.8之前,采用分段(分桶)锁, 分段锁采用的是读写锁机制(读锁:允许多个线程读,但是不允许线程写;写锁:允许一个线程写,但是不允许线程读);jdk1.8不再采用锁机制,而是CAS(Compare and Swap)算法, 减小了锁的开销;如果一个桶中的元素个数超过了8个,那么会将这个桶的链表扭转成一棵红黑树(自平衡二叉查找树)结构。
红黑树
自平衡二叉查找树 — 时间复杂度O(logn)
特征:
1. 每一个节点非红即黑
2. 根节点一定是黑色
3. 所有的叶子节点一定是黑色的nil节点
4. 红节点的子节点一定是黑节点
5. 任意一条路径中的黑色节点个数一致
6. 插入的节点一定是红色
修复:
1. 当前节点为红,并且父节点且叔父节点为红,那么将父节点以及叔父节点涂黑,然后将祖父节点涂红
2. 当前节点为红,并且父节点为红且叔父节点为黑,当前节点为右子叶,以当前节点为轴进行左旋
3.当前节点为红,并且父节点为红且叔父节点为黑,当前节点为左子叶,以父节点为轴进行右旋
三、ConcurrentNavigableMap - 并发导航映射
本身是一个接口,所以更多的是使用实现类 - ConcurrentSkipListMap - 并发跳跃表映射
跳跃表:为了提高查询效率所产生一种数据结构 — 跳跃表是典型的以空间换时间的产物
如果跳跃表中插入新的元素,新的元素是否往上提取遵循"抛硬币"原则 --1/2原则 - 只要保证这个节点有一半的概率被提取就可以
跳跃表适合于大量查询而不增删的场景
扩展:B+树是MySQl的索引的建立机制
四、锁
1.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();
}
}
2.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 + "选手听到枪响,跑了出去~~~~");
}
}
3.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();
}
}
}
4.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();
}
}