- 本章节主要是讲juc包下的并发容器,以及如何实现线程安全的源码。
ConcurrentSkipListMap
该结构是一个特殊的数据结构,是一种链表的结构目的就是为了能快速的查找。它的实现思路为添加类似索引的机制,索引包含节点。当索引数据量比较多的时候,又会建立索引分级。就是基于索引层次在创建一层索引。如上图。
- 源码分析(put)
*/ // 这个方法目的就是查找到,这个新数据要插入到哪个节点的后面
private Node<K,V> findPredecessor(Object key, Comparator<? super K> cmp) {
if (key == null)
throw new NullPointerException(); // don't postpone errors
for (;;) {
for (Index<K,V> q = head, r = q.right, d;;) {// 从右边找索引
if (r != null) { // 当前level下存在合适的index
Node<K,V> n = r.node;
K k = n.key;
if (n.value == null) { // 如果node的value为空,这个节点就不需要了,
if (!q.unlink(r))// 当前level的链表中丢弃这个节点,将链表下下一个节点顶上来
break; // restart 如果没有后续节点,则重试
r = q.right; // reread r // 如果有,则继续寻找当前level中右边的index
continue;
}
if (cpr(cmp, key, k) > 0) { // 如果当前插入的key大于查找到的key,则继续往右查找(这个比较自己定义)
q = r;
r = r.right;
continue;
}
}
if ((d = q.down) == null) // 找到最底层level的index
return q.node; // 如果本身就是最低level,则返回该节点
q = d; // 如果还有低一个level的索引,则继续寻找
r = d.right; // 那就继续寻找
}
}
}
private V doPut(K key, V value, boolean onlyIfAbsent) {
Node<K,V> z; // added node
if (key == null)
throw new NullPointerException();
Comparator<? super K> cmp = comparator;
outer: for (;;) {// 查找这个节点应该塞到哪一个节点后面 cas+循环,无锁,不断重试
for (Node<K,V> b = findPredecessor(key, cmp), n = b.next;;) {
if (n != null) { // n 代表next位置,不为空则代表已经有数据存在,应该继续寻找后续的位置
Object v; int c;
Node<K,V> f = n.next;
if (n != b.next) // inconsistent read 如果next发生了变化,表示出现脏读,则退出重试
break;
if ((v = n.value) == null) { // n is deleted // 如果next节点的内容置空了,则应该删除该节点
n.helpDelete(b, f);
break;
}
if (b.value == null || v == n) // b is deleted // 如果当前node内容置空了,则退出重试
break;
if ((c = cpr(cmp, key, n.key)) > 0) { // 比较next和当前要插入的key,如果大于next的key,则继续寻找后续的next
b = n;
n = f;
continue;
}
if (c == 0) { // c 比对的结果,如果为0,则代表key相等,相等则直接cas替换
if (onlyIfAbsent || n.casValue(v, value)) {
@SuppressWarnings("unchecked") V vv = (V)v;
return vv;
}
break; // restart if lost race to replace value
}
// else c < 0; fall through
}
// 如果找到了合适的前置节点
z = new Node<K,V>(key, value, n); //
if (!b.casNext(n, z)) // CAS 将当前数据塞到前置节点后面
break; // restart if lost race to append to b // 如果失败,继续循环
break outer;
}
}
// 上面的代码,是将node插入到合适的位置。接下来就需要重构一下skiplist,判断是否需要增加level
// Tony: 防止源码编译报错,我注释掉了后面一段
int rnd = 0; // ThreadLocalRandom.nextSecondarySeed(); // 这里的代码是生成随机数
if ((rnd & 0x80000001) == 0) { // test highest and lowest bits // 随机数是正偶数,则升级level
int level = 1, max;
while (((rnd >>>= 1) & 1) != 0) // 根据随机数计算level。从低2位开始向左有多少个连续的1
++level;
Index<K,V> idx = null;
HeadIndex<K,V> h = head;
if (level <= (max = h.level)) {// 随机计算出来的level小于当前的level级别,不需要增加层级
for (int i = 1; i <= level; ++i)
idx = new Index<K,V>(z, idx, null); // 构建一个index,归属于随机到的level
}
else { // try to grow by one level // 升级
level = max + 1; // hold in array and later pick the one to use // 每次升一级
@SuppressWarnings("unchecked")Index<K,V>[] idxs =
(Index<K,V>[])new Index<?,?>[level+1]; // 创建一个index集合,数量是level数量
for (int i = 1; i <= level; ++i) // 给上面新建的数组,赋值(idxs好像没什么卵用)
idxs[i] = idx = new Index<K,V>(z, idx, null);
for (;;) {
h = head;
int oldLevel = h.level;
if (level <= oldLevel) // 新level小于就得level,代表其他线程更新了level,此次升级失败。
break;
HeadIndex<K,V> newh = h;
Node<K,V> oldbase = h.node;
for (int j = oldLevel+1; j <= level; ++j)
newh = new HeadIndex<K,V>(oldbase, newh, idxs[j], j); // 生成一个新的headIndex。最高level
if (casHead(h, newh)) { // 将head引用到新的index
h = newh;
idx = idxs[level = oldLevel];
break;
}
}
}
// find insertion points and splice in //将新的Index插入对应level链表上
splice: for (int insertionLevel = level;;) {
int j = h.level;
for (Index<K,V> q = h, r = q.right, t = idx;;) {// 从head index开始找位置
if (q == null || t == null)
break splice;
if (r != null) { // 比对,找到level中合适的节点进行插入
Node<K,V> n = r.node;
// compare before deletion check avoids needing recheck
int c = cpr(cmp, key, n.key);
if (n.value == null) {
if (!q.unlink(r))
break;
r = q.right;
continue;
}
if (c > 0) {
q = r;
r = r.right;
continue;
}
}
if (j == insertionLevel) {
if (!q.link(r, t))
break; // restart
if (t.node.value == null) {
findNode(key);
break splice;
}
if (--insertionLevel == 0)
break splice;
}
if (--j >= insertionLevel && j < level)
t = t.down;
q = q.down;
r = q.right;
}
}
}
return null;
}
- 案例演示
public class ConcurrentSkipListMapDemo {
public static void main(String[] args) {
// 可以自己定义比较方式
ConcurrentSkipListMap<String, String> map = new ConcurrentSkipListMap<>(new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
// 自定义 key 的比较方式
return 0;
}
});
map.put("a", "1");
map.put("c", "3");
map.put("b", "2");
System.out.println(map.keySet().toString());
}
}
- 执行结果
[a]
CopyOnWriteArrayList
- 如果我们在遍历List的时候删除会有什么问题?
- 案例演示
public class ArrayListDemo {
public static void main(String[] args) {
ArrayList<String> arrayList = new ArrayList<>();
arrayList.add("a");
arrayList.add("b");
arrayList.add("c");
Iterator<String> iterator = arrayList.iterator();
while (iterator.hasNext()) {
// iterator.remove();// IllegalStateException
arrayList.remove(iterator.next()); // ConcurrentModificationException
}
}
}
- 执行结果
报错
首先存储方式是数组存储。在执行的时候,先讲Array复制一份出新的数据,之后在set进去。
- 案例演示
public class CopyOnWriteArrayListDemo {
public static void main(String[] args) {
CopyOnWriteArrayList<String> arrayList = new CopyOnWriteArrayList<>();
arrayList.add("a");
arrayList.add("b");
arrayList.add("c");
Iterator<String> iterator = arrayList.iterator();
while (iterator.hasNext()) {
// iterator.remove();// IllegalStateException
arrayList.remove(iterator.next()); // ConcurrentModificationException
}
}
}
额外的类
HashSet底层本质是使用HashMap进行存储,在增加元素的时候把元素作为map的key进行存储。最终将所有的key返回回来。
// 测试将对象作为key
public class ObjectHashMapDemo {
public static void main(String[] args) {
HashMap<User, String> map = new HashMap<>();
User user = new User("tony");
map.put(user, "test");
System.out.println(map.get(user)); // 1、 输出什么 test
user = new User("tony");
System.out.println(map.get(user)); // 2、 输出什么 null
}
}
class User {
public String name;
public User(String name) {
this.name = name;
}
// 如果不重写的话,输出结果第二个为null
// @Override
// public boolean equals(Object obj) {
// return name.equals(((User)obj).name);
// }
//
// @Override
// public int hashCode() {
// return name.hashCode();
// }
}
Queen
非阻塞的全部实现Queen接口的定义,阻塞的全部实现BlockingQueen
- 案例演示
// 它是基于数组的阻塞循环队列, 此队列按 FIFO(先进先出)原则对元素进行排序。
public class ArrayBlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
// 构造时需要指定容量(量力而行),可以选择是否需要公平(最先进入阻塞的,先操作)
ArrayBlockingQueue<String> queue = new ArrayBlockingQueue<>(3, false);
// 1秒消费数据一个
new Thread(() -> {
while (true) {
try {
System.out.println("取到数据:" + queue.poll()); // poll非阻塞
Thread.sleep(1000L);
} catch (InterruptedException e) {
}
}
}).start();
Thread.sleep(3000L); // 让前面的线程跑起来
// 三个线程塞数据
for (int i = 0; i < 6; i++) {
new Thread(() -> {
try {
queue.put(Thread.currentThread().getName()); // put阻塞(如果当前的队列已经塞满了数据,线程不会继续往下执行,等待其他线程把
// 队列的数据拿出去// )
// queue.offer(Thread.currentThread().getName()); // offer非阻塞,满了返回false
System.out.println(Thread.currentThread() + "塞入完成");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
PriorityQueue
// 是一个带优先级的 队列,而不是先进先出队列。
// 元素按优先级顺序被移除,该队列也没有上限
// 没有容量限制的,自动扩容
// 虽然此队列逻辑上是无界的,但是由于资源被耗尽,所以试图执行添加操作可能会导致 OutOfMemoryError),
// 但是如果队列为空,
// 那么取元素的操作take就会阻塞,所以它的检索操作take是受阻的。另外,
// 入该队列中的元素要具有比较能力
public class PriorityQueueDemo {
public static void main(String[] args) {
// 可以设置比对方式
PriorityQueue<String> priorityQueue = new PriorityQueue<>(new Comparator<String>() {
@Override //
public int compare(String o1, String o2) {
// 实际就是 元素之间的 比对。
return 0;
}
});
priorityQueue.add("c");
priorityQueue.add("a");
priorityQueue.add("b");
System.out.println(priorityQueue.poll());
System.out.println(priorityQueue.poll());
System.out.println(priorityQueue.poll());
PriorityQueue<MessageObject> MessageObjectQueue = new PriorityQueue<>(new Comparator<MessageObject>() {
@Override
public int compare(MessageObject o1, MessageObject o2) {
return o1.order > o2.order ? -1 : 1;
}
});
}
}
class MessageObject {
String content;
int order;
}
DelayQueue
// (基于PriorityQueue来实现的)是一个存放Delayed 元素的无界阻塞队列,
// 只有在延迟期满时才能从中提取元素。该队列的头部是延迟期满后保存时间最长的 Delayed 元素。
// 如果延迟都还没有期满,则队列没有头部,并且poll将返回null。
// 当一个元素的 getDelay(TimeUnit.NANOSECONDS) 方法返回一个小于或等于零的值时,
// 则出现期满,poll就以移除这个元素了。此队列不允许使用 null 元素。
public class DelayQueueDemo {
public static void main(String[] args) throws InterruptedException {
DelayQueue<Message> delayQueue = new DelayQueue<Message>();
// 这条消息5秒后发送
Message message = new Message("message - 00001", new Date(System.currentTimeMillis() + 5000L));
delayQueue.add(message);
while (true) {
System.out.println(delayQueue.poll());
Thread.sleep(1000L);
}
// 线程池中的定时调度就是这样实现的
}
}
// 实现Delayed接口的元素才能存到DelayQueue
class Message implements Delayed {
// 判断当前这个元素,是不是已经到了需要被拿出来的时间
@Override
public long getDelay(TimeUnit unit) {
// 默认纳秒
long duration = sendTime.getTime() - System.currentTimeMillis();
return TimeUnit.NANOSECONDS.convert(duration, TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
return o.getDelay(TimeUnit.NANOSECONDS) > this.getDelay(TimeUnit.NANOSECONDS) ? 1 : -1;
}
String content;
Date sendTime;
/**
* @param content 消息内容
* @param sendTime 定时发送
*/
public Message(String content, Date sendTime) {
this.content = content;
this.sendTime = sendTime;
}
@Override
public String toString() {
return "Message{" +
"content='" + content + '\'' +
", sendTime=" + sendTime +
'}';
}
}
ConcurrentLinkedQueue
// 优势:无锁。
// 注意:批量操作不提供原子保证 addAll, removeAll, retainAll, containsAll, equals, and toArray
// 坑: size()方法每次都是遍历整个链表,最好不要频繁调用
// 如果没有阻塞要求,用这个挺好的(堆积数据)
public class ConcurrentLinkedQueueDemo {
public static void main(String[] args) throws InterruptedException {
// 不需要指定容量,addAll
ConcurrentLinkedQueue<String> queue = new ConcurrentLinkedQueue<String>();
// 1秒消费数据一个
new Thread(() -> {
while (true) {
try {
System.out.println("取到数据:" + queue.poll()); // poll非阻塞
Thread.sleep(1000L);
} catch (InterruptedException e) {
}
}
}).start();
Thread.sleep(3000L); // 让前面的线程跑起来
// 三个线程塞数据
for (int i = 0; i < 6; i++) {
new Thread(() -> {
try {
queue.offer(Thread.currentThread().getName()); // offer非阻塞,满了返回false
System.out.println(Thread.currentThread() + "塞入完成");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
}
SynchronousQueue
// 这是一个神奇的队列, 因为他不存数据。 手把手的交互数据
public class SynchronousQueueDemo {
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<String> synchronousQueue = new SynchronousQueue<>();
// synchronousQueue.add("a"); // IllegalStateException
// synchronousQueue.offer("a");
System.out.println(synchronousQueue.poll()); // 非阻塞
// 阻塞式的用法
new Thread(() -> {
try {
System.out.println("等数据....");
System.out.println(synchronousQueue.take());
System.out.println("执行完毕....");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
Thread.sleep(1000L);
System.out.println("准备赛数据了");
synchronousQueue.put("a");// 等待有人取走他
}
}