最近将自己之前的知识整理整合一遍,防止遗漏忘记。
Concurrent
concurrent包是jdk1.5开始所提供的一个针对高并发进行编程的包。
一.阻塞式队列 - BlockingQueue
遵循先进先出(FIFO)原则。阻塞式队列本身使用的时候需要指定界限。
1.ArrayBlockingQueue-阻塞式顺序队列
底层是基于数组来进行储存的,使用时需要指定一个容量,容量在指定之后不可改变。多用于生产-消费模型。
示例代码:
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");
// 如果队列已满,则抛出异常 - IllegalStateException
// queue.add("a");
// 返回值标记元素是否成功添加到队列里面
// 如果队列已满,则返回false
// boolean b = queue.offer("b");
// System.out.println(b);
// 如果队列已满,会产生阻塞 --- 直到这个队列中有元素被取出,才会放开阻塞
// queue.put("c");
// 队列为空,抛出异常:NoSuchElementException
// String str = queue.remove();
// 队列为空,返回null
// String str = queue.poll();
// 队列为空,产生阻塞 - 直到有其他线程向队列中添加元素,才会放开阻塞
// String str = queue.take();
// 定时阻塞
// 在3s之内如果有元素被取出,那么元素就会添加到队列中
// 如果3s之后队列依然是满的,那么返回false表示添加失败
boolean b = queue.offer("d", 3000, TimeUnit.MILLISECONDS);
System.out.println(b);
System.out.println(queue);
}
2.LinkedBlockingQueue-阻塞式链式队列
底层时基于链表(节点)来进行数据的储存。在使用的时候可以指定初始容量,也可以不指定。如果指定了容量,就以指定的容量为准来进行储存;如果不指定容量,默认容量是Integer.MAX_VALUE->2^31-1,一般认为这个容量是无限的。
3.PriorityBlockingQueue-具有优先级的阻塞式队列
如果不指定容量,默认容量是11.如果将元素依次取出,那么会对元素进行自然排序,要求存储的对象对应的类必须实现Comparable类,重写compareTo方法,将自定的比较规则写入方法中;如果进行迭代遍历,则不会保证排序。
示例代码:
public class PriorityBlockingQueueDemo2 {
public static void main(String[] args) throws InterruptedException {
PriorityBlockingQueue<Student> queue = new PriorityBlockingQueue<>();
queue.put(new Student("程咬金", 15, 59));
queue.put(new Student("秦琼", 18, 89));
queue.put(new Student("王世充", 80, 48));
queue.put(new Student("李元霸", 12, 90));
for (Student s : queue) {
System.out.println(s);
}
for(int i = 0; i < 4; i++){
System.out.println(queue.take());
}
}
}
class Student implements Comparable<Student>{
private String name;
private int age;
private int score;
public Student(String name, int age, int score) {
super();
this.name = name;
this.age = age;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + ", score=" + score + "]";
}
// @Override
// // 指定比较规则
// // 如果返回值>0,那么this就要排到o的后边
// // 如果返回值<0,那么this就要排到o的前边
// public int compareTo(Student o) {
// return this.age - o.age;
// }
@Override
public int compareTo(Student o) {
return o.score - this.score;
}
}
4.SynchronousQueue-同步队列
队列只允许存储一个元素。
二.并发映射 - ConcurrentMap
1.hashMap
底层依靠数组加链表储存的数据。默认初始容量是16,默认加载因子是0.75f,默认扩容每次增加一倍。本身是一个异步式线程不安全的映射。
2.HashTable
同步式线程安全的映射,对外提供的方法都是同步方法。
3.ConcurrentHashMap
异步式线程安全的映射,在jdk1.8之前采用分段锁,分段锁采用的式读写锁机制(读锁:允许多个线程读,但不允许线程写;写锁:允许一个线程写,但不允许线程读),jdk1.8不再采用锁机制,而是CAS(Compare and Swap)算法,减少了锁的开销。如果一个段(桶)中的元素超过了8个,那么会将这个段(桶)的链表扭转成一棵红黑树(自平衡二叉查找树)结构。
补充:红黑树的修正过程
1.当前节点为红色,且父节点以及叔父节点为红色,那么将父节点以及叔父节点但涂黑,将祖父节点涂红。
2.当前节点为红色,并且是右子叶,父节点为红且叔父节点为黑,那么以当前节点为基准进行左旋。
3.当前节点为红色,并且是左子叶,父节点为红且叔父节点为黑,那么以父节点为基准进行右旋。
红黑树的查找的时间复杂度为:O(logn)。
ConcurrentNavigableMap - 并发导航映射
其本身为一个接口,所以更多的是使用实现类—ConcurrentSkipListMap–并发跳跃表映射
跳跃表:为了提高查询效率所产生的一种数据结构—跳跃表是典型的以空间换时间的产物。
如果跳跃表中插入新的元素,新的元素是否往上提取遵循“抛硬币”原则–二分之一原则,只要保证这个节点有一半的概率被提取就可以了。所以跳跃表一般只适合大量查询而不增删的场景。
三.锁
CountDownLatch-闭锁
线程递减锁,对线程进行技术,当计数归零的时候会放开阻塞让线程继续往下执行。
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch cdl = new CountDownLatch(5);
new Thread(new Teacher(cdl)).start