说说 CountDownLatch 与 CyclicBarrier 区别
1.CountDownLatch 的作用是允许 1 或 N 个线程等待其他线程完成执行;而 CyclicBarrier 则是允许 N 个线程相互等待。 2.CountDownLatch 的计数器无法被重置; CyclicBarrier 的计数器可以被重置后使用,因此它被称为是循环的 barrier。 两条核心区别准确,但只回答了”是什么”。要能在设计中选对工具,还需要:两者的执行模型差异(谁等谁、何时放行)、barrierAction 的独特价值、以及各自失效场景。本文用模型图景 + 代码对照补全。 执行模型对照CountDownLatch(门闩):计数器从 N 开始,countDown() 减一(通常由工作线程调用),await() 阻塞等待归零(通常由协调线程调用)。方向是”多对一/多对多”的事件等待:等待方关心的是”N 件事都做完了”,不关心是谁做的。 CountDownLatch latch = new CountDownLatch(3);pool.submit(() -> { loadUser(); latch.countDown(); ...
说说 CountDownLatch 原理
CountDownLatch 解决的是一个非常具体的并发协调问题:一个(或多个)线程需要等待另外 N 个线程全部完成后,才能继续执行。典型如首页聚合接口:并行调用用户服务、订单服务、推荐服务,三个都返回后才能组装响应。本文从用法到 AQS 源码层把原理讲透。 用法模型CountDownLatch latch = new CountDownLatch(3);for (String svc : List.of("user", "order", "recommend")) { pool.submit(() -> { try { call(svc); // 并行发起远程调用 } finally { latch.countDown(); // 完成一个,计数减一 } });}latch.await(); ...
sleep、join、yield 有什么区别
sleep()sleep() 方法需要指定等待的时间,它可以让当前正在执行的线程在指定的时间内暂停执行,进入阻塞状态,该方法既可以让其他同优先级或者高优先级的线程得到执行机会,也可以让低优先级的线程得到执行机会。但是 sleep() 方法不会释放”锁标志”,也就是说如果有 synchronized 同步块,其他线程仍然不能访问共享数据。 wait()wait() 方法需要和 notify() 及 notifyAll() 两个方法一起介绍,这三个方法用于协调多个线程对共享数据的存取,所以必须在 synchronized 语句块内使用,也就是说,调用 wait(),notify() 和 notifyAll() 的任务在调用这些方法前必须拥有对象的锁。注意,它们都是 Object 类的方法,而不是 Thread 类的方法。 wait() 方法与 sleep() 方法的不同之处在于,wait() 方法会释放对象的”锁标志”。当调用某一对象的 wait() 方法后,会使当前线程暂停执行,并将当前线程放入对象等待池中,直到调用了 notify() 方法后,将从对象等待池中移出任意一个线程并放...
创建线程的方式及实现
继承 Thread 类创建线程类 定义 Thread 类的子类,并重写该类的 run 方法,该 run 方法的方法体就代表了线程要完成的任务。因此把 run() 方法称为执行体。 创建 Thread 子类的实例,即创建了线程对象。 调用线程对象的 start() 方法来启动该线程。 通过 Runnable 接口创建线程类 定义 Runnable 接口的实现类,并重写该接口的 run() 方法,该 run() 方法的方法体同样是该线程的线程执行体。 创建 Runnable 实现类的实例,并依此实例作为 Thread 的 target 来创建 Thread 对象,该 Thread 对象才是真正的线程对象。 调用线程对象的 start() 方法来启动该线程。 通过 Callable 和 Future 创建线程 创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。 创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callabl...
ConcurrentHashMap 的工作原理及代码实现
ConcurrentHashMap 采用了非常精妙的”分段锁”策略,ConcurrentHashMap 的主干是个 Segment 数组。Segment 继承了 ReentrantLock,所以它就是一种可重入锁(ReentrantLock)。在 ConcurrentHashMap,一个 Segment 就是一个子哈希表,Segment 里维护了一个 HashEntry 数组,并发环境下,对于不同 Segment 的数据进行操作是不用考虑锁竞争的。 这段描述对应的是 JDK7 的实现。JDK8 已经彻底重写了这套机制——抛弃 Segment,改为 CAS + synchronized 锁桶头节点,锁粒度从”段”细化到”单个桶”。两个版本都值得理解:7 看思想,8 看工程。 JDK7:Segment 分段锁 结构:Segment[],每个 Segment 内含一个 HashEntry[] 并继承 ReentrantLock 并发度:默认 16 个 Segment,最多 16 个写线程互不阻塞 局限:锁粒度固定为段,段内冲突依然串行;size() 需要重试或加锁统计 分段锁的意义在...
HashMap 的工作原理及代码实现
HashMap 基于 hashing 原理,我们通过 put() 和 get() 方法储存和获取对象。当我们将键值对传递给 put() 方法时,它调用键对象的 hashCode() 方法来计算 hashcode,让后找到 bucket 位置来储存值对象。当获取对象时,通过键对象的 equals() 方法找到正确的键值对,然后返回值对象。HashMap 使用链表来解决碰撞问题,当发生碰撞了,对象将会储存在链表的下一个节点中。 HashMap 在每个链表节点中储存键值对对象。 以上是哈希表的通用原理,但 JDK8 的 HashMap 远不止”数组+链表”这么简单。下面按结构、put 流程、扩容、线程安全四个层面拆透。 结构:数组 + 链表 + 红黑树JDK8 中 HashMap 的桶(bucket)里挂的节点有两种形态: 链表:冲突少时的默认形态,查找 O(n) 红黑树:当链表长度 ≥ 8 且数组长度 ≥ 64 时树化,查找退化为 O(logN) 树化阈值 8 不是拍脑袋:泊松分布下链表长度达到 8 的概率约千万分之六,所以树化是”极端冲突的保险”,正常情况几乎不触发;而数组不足...
HashMap 和 ConcurrentHashMap 的区别.md
放入 HashMap 的元素是 key-value 对。 底层说白了就是散列结构。 要将元素放入到 HashMap 中,那么 key 的类型必须要实现 hashcode 方法,默认这个方法是根据对象的地址来计算的,接着还必须覆盖对象的 equals() 方法。 ConcurrentHashMap 对整个桶数组进行了分段,而 HashMap 则没有 ConcurrentHashMap 在每一个分段上都用锁进行保护,从而让锁的粒度更精细一些,并发性能更好,而 HashMap 没有锁机制,不是线程安全的 五条要点方向正确,但”分段锁”是 JDK7 的描述,JDK8 已改为 CAS + synchronized 桶级锁;且两者差异不止同步机制,还有 null 语义、迭代器一致性、size 统计等。本文按”结构同源、并发异构”的主线补全。 同源:散列结构与键契约两者底层都是”数组 + 链表/红黑树”(JDK8),都依赖 key 的 hashCode 定位桶、equals 判等。key 契约相同:重写 equals 必须重写 hashCode,否则检索错乱。 put/...
HashMap 和 HashTable 的区别
HashMap 几乎可以等价于 HashTable,除了 HashMap 是非 synchronized 的,并可以接受 null(HashMap 可以接受为 null 的键值 (key) 和值 (value),而 HashTable 则不行)。 HashMap 是非 synchronized,而 HashTable 是 synchronized,这意味着 HashTable 是线程安全的,多个线程可以共享一个 HashTable;而如果没有正确的同步的话,多个线程是不能共享 HashMap 的。Java 5 提供了 ConcurrentHashMap,它是 HashTable 的替代,比 HashTable 的扩展性更好。 另一个区别是 HashMap 的迭代器 (Iterator) 是 fail-fast 迭代器,而 HashTable 的 enumerator 迭代器不是 fail-fast 的。所以当有其它线程改变了 HashMap 的结构(增加或者移除元素),将会抛出 ConcurrentModificationException,但迭代器本身的 remove()...
HashSet 和 HashMap 区别
HashMap HashSet HashMap 实现了 Map 接口 HashSet 实现了 Set 接口 HashMap 储存键值对 HashSet 仅仅存储对象 使用 put() 方法将元素放入 map 中 使用 add() 方法将元素放入 set 中 HashMap 中使用键对象来计算 hashcode 值 HashSet 使用成员对象来计算 hashcode 值,对于两个对象来说 hashcode 可能相同,所以 equals() 方法用来判断对象的相等性,如果两个对象不同的话,那么返回 false HashMap 比较快,因为是使用唯一的键来获取对象 HashSet 较 HashMap 来说比较慢 表格列出了 API 层区别,但最有价值的事实藏在源码里:HashSet 就是 HashMap 包了一层。理解这层包装关系,两者的一切异同都能推导出来。 源码关系:HashSet 是 HashMap 的适配器JDK 中 HashSet 的内部实现极其简单: private transient HashMap<E, Object> ma...
ArrayList 与 Vector 区别
同步性:Vector 是线程安全的,也就是说是同步的 ,而 ArrayList 是线程不安全的,不是同步的。 数据增长:当需要增长时,Vector 默认增长为原来一倍 ,而 ArrayList 却是原来的 50% ,这样 ArrayList 就有利于节约内存空间。 说明:如果涉及到堆栈,队列等操作,应该考虑用 Vector,如果需要快速随机访问元素,应该使用 ArrayList 两条区别都对,但都停留在 JDK1.0 时代的认知。今天再谈 Vector,重点应是:为什么它被弃用、”线程安全”为什么名不副实、以及并发 List 的正确选型。 表面区别补全 维度 ArrayList Vector 同步 非同步 方法级 synchronized 扩容 1.5 倍 默认 2 倍(可配 capacityIncrement) 迭代器 Iterator fail-fast Iterator fail-fast + Enumeration(非 fail-fast) 出身 JDK1.2 集合框架 JDK1.0 遗留类 现状 默认选择 遗留兼容,新代码禁用 扩容差异...















