Java并发 -- Future
ThreadPoolExecutor12345678910// 无法获取任务的执行结果public void execute(Runnable command);// 可以获取任务的执行结果// Runnable接口的run()方法没有返回值,返回的Future只能断言任务是否已经结束,类似于Thread.join()public Future<?> submit(Runnable task)// Callable接口的call()方法有返回值,可以通过调用返回的Future对象的get()方法获取任务的执行结果public <T> Future<T> submit(Callable<T> task);// 返回的Future对象f,f.get()的返回值就是传给submit方法的参数resultpublic <T> Future<T> submit(Runnable task, T result) Future1234567891011// 取消任务boolean cancel(boolean mayInterruptIfR...
Java并发 -- 线程池
创建线程 创建普通对象,只是在JVM的堆里分配一块内存而已 创建线程,需要调用操作系统内核的API,然后操作系统需要为线程分配一系列资源,成本很高 线程是一个重量级对象,应该避免频繁创建和销毁,采用线程池方案 一般的池化资源12345678910111213141516// 假设Java线程池采用一般意义上池化资源的设计方法class ThreadPool { // 获取空闲线程 Thread acquire() { } // 释放线程 void release(Thread t) { }}// 期望的使用ThreadPool pool;Thread T1 = pool.acquire();// 传入Runnable对象T1.execute(() -> { // 具体业务逻辑}); 生产者-消费者模式业界线程池的设计,普遍采用生产者-消费者模式,线程池的使用方是生产者,线程池本身是消费者 123456789101112131415161718192021222324...
Java并发 -- 原子类
add12345678private long count = 0;public void add() { int idx = 0; while (idx++ < 10_000) { count += 1; }} add()方法不是线程安全的,主要原因是count的可见性和count+=1的原子性 可见性的问题可以用volatile来解决,原子性的问题一般采用互斥锁来解决 无锁方案12345678private AtomicLong atomicCount = new AtomicLong(0);public void atomicAdd() { int idx = 0; while (idx++ < 10_000) { atomicCount.getAndIncrement(); }} 无锁方案相对于互斥锁方案,最大的好处是_性能_ 互斥锁方案为了保证互斥性,需要执行加锁、解锁操作,而加锁、解锁操作本身会消耗性能 拿不到锁的线程...
Java并发 -- 并发容器
同步容器 Java 1.5之前提供的同步容器虽然也能保证线程安全,但性能很差 Java中的容器主要分为四大类,分别为List、Map、Set和Queue,并不是所有的Java容器都是线程安全的 将非线程安全的容器变成线程安全的容器的简单方案:synchronized 把非线程安全的容器封装在对象内部,然后控制好访问路径即可 线程安全的ArrayList12345678910111213141516171819public class SafeArrayList<T> { private List<T> list = new ArrayList<>(); public synchronized T get(int idx) { return list.get(idx); } public synchronized void add(int idx, T t) { list.add(idx, t); } public synchronized boo...
Java并发 -- CountDownLatch + CyclicBarrier
对账系统 1234567891011// 存在未对账订单while (existUnreconciledOrders()) { // 查询未对账订单 pOrder = getPOrder(); // 查询派送订单 dOrder = getDOrder(); // 执行对账操作 Order diff = check(pOrder, dOrder); // 将差异写入差异库 save(diff);} 性能瓶颈 getPOrder()和getDOrder()最为耗时,并且两个操作没有先后顺序的依赖,可以并行处理 简单并行 - join 123456789101112131415161718192021222324// 存在未对账订单// 存在未对账订单while (existUnreconciledOrders()) { // 查询未对账订单 Thread t1 = new Thread(() -> { pOrder = getPOrder(); }); ...
Java并发 -- StampedLock
StampedLock VS ReadWriteLock StampedLock同样适用于读多写少的场景,性能比ReadWriteLock好 ReadWriteLock支持两种模式:写锁、读锁 StampedLock支持三种模式:写锁、悲观读锁、_乐观读_(关键) StampedLock的写锁、悲观读锁的语义和ReadWriteLock的写锁、读锁的语义非常类似 允许多个线程同时获取悲观读锁,只允许一个线程获取写锁,写锁和悲观读锁是互斥的 但StampedLock里的写锁和悲观读锁加锁成功之后,都会返回一个stamp,然后解锁的时候需要传入这个stmap 12345678910111213141516171819202122232425public class StampedLockExample { private final StampedLock stampedLock = new StampedLock(); @Test // 悲观读锁 public void pessimisticReadLockTest() { long...
Java并发 -- ReadWriteLock
读多写少 理论上,利用管程和信号量可以解决所有并发问题,但JUC提供了很多工具类,_细分场景优化性能,提升易用性_ 针对读多写少的并发场景,JUC提供了读写锁,即ReadWriteLock 读写锁 读写锁是一种广泛使用的通用技术,并非Java所特有 所有读写锁都遵守3条基本原则 允许多个线程同时读共享变量 – 与互斥锁的重要区别 只允许一个线程写共享变量 如果一个写线程正常执行写操作,此时禁止读线程读取共享变量 缓存1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253public class Cache<K, V> { private final Map<K, V> map = new HashMap<>(); private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); // 读锁 private...
Java并发 -- Semaphore
历史 信号量是由计算机科学家Dijkstra在1965年提出,在之后的15年,信号量一直都是并发编程领域的终结者 直到1980年管程被提出来,才有了第二选择,目前所有支持并发编程的语言都支持信号量机制 信号量模型 在信号量模型里,计数器和等待队列对外都是透明的,只能通过信号量模型提供的三个方法来访问它们,即init/down/up init():设置计数器的初始值 down():计数器的值减1,如果此时计数器的值小于0,则当前线程将阻塞,否则当前线程可以继续执行 up():计数器加1,如果此时计数器的值大于或等于0,则唤醒等待队列中的一个线程,并将其从等待队列中移除 init/down/up都是原子性的,这个原子性由信号量模型的实现方保证 在JUC中,信号量模型由java.util.concurrent.Semaphore实现,Semaphore能够保证这三个方法的原子性操作 在信号量模型里,down/up这两个操作最早被称为P操作和V操作,因此信号量模型也被称为_PV原语_ 在JUC中,down和up对应的是acquire和release...
Java并发 -- Condition
Condition Condition实现了管程模型中的_条件变量_ Java内置的管程(synchronized)只有一个条件变量,而Lock&Condition实现的管程支持多个条件变量 在很多并发场景下,支持多个条件变量能够让并发程序的可读性更好,也更容易实现 阻塞队列1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253// 下列三对操作的语义是相同的// Condition.await() Object.wait()// Condition.signal() Object.notify()// Condition.signalAll() Object.notifyAll()public class BlockedQueue<T> { private static final int MAX_SIZE = 10; // 可重入锁 private final Lock loc...
Java核心 -- Vector + ArrayList + LinkedList
概述 Vector、ArrayList、LinkedList都实现了java.util.List接口,即有序集合 Vector是Java早期提供的线程安全的动态数组,同步有一定的额外开销 Vector内部使用对象数组来保存数据,可以自动扩容(创建新的数组,拷贝原有数组的数据) ArrayList是应用更加广泛的动态数组,本身并不是线程安全的,性能比Vector要好很多 Vector的自动扩容是增加1倍,而ArrayList的自动扩容是增加50% LinkedList是双向链表,不是线程安全的,也不需要动态调整容量 适用场景 Vector和ArrayList都是动态数组,其内部元素是以数组的形式存储的,非常适合随机访问的场合 除了在尾部插入和删除元素,性能都比较差,因为要移动后续所有的元素 LinkedList进行元素的插入和删除操作会高效很多,但随机访问性能要比动态数组差 集合 容器包括集合和Map,_Map并不是真正的集合_ List:有序结合 Set:不允许重复元素(equals判断) Queue/Deque:标准队列,支持FIFO或者LIFO 每种集合的通用逻辑,...














