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 每种集合的通用逻辑,...
Java并发 -- Lock
管程 并发领域的两大核心问题:互斥 + 同步 互斥:同一时刻只允许一个线程访问共享资源 同步:线程之间的通信和协作 JUC通过Lock和Condition两个接口实现管程,其中Lock用于解决互斥问题,而Condition用于解决同步问题 再造管程的理由 Java语言对管程的原生实现:synchronized 在Java 1.5中,synchronized的性能不如JUC中的Lock,在Java 1.6中,synchronized做了很多的性能优化 再造管程的核心理由:synchronized无法破坏不可抢占条件(死锁的条件之一) synchronized在申请资源的时候,如果申请不到,线程直接进入阻塞状态,也不会释放线程已经占有的资源 更合理的情况:占用部分资源的线程如果进一步申请其它资源的时,如果申请不到,可以主动释放它所占有的资源 解决方案 能够响应中断 synchronized:持有锁A的线程在尝试获取锁B失败,进入阻塞状态,如果发生死锁,将没有机会唤醒阻塞线程 如果处于阻塞状态的线程能够响应中断信号,那阻塞线程就有机会释放曾经持有的锁A 支持超时 如果线程在一段时间内没有获得锁,...
Java核心 -- int + Integer
包装类 Integer是int对应的包装类,里面有一个int类型的字段存储数据,并提供了基本的操作 在Java 5,引入了自动装箱和自动拆箱(boxing/unboxing),Java可以根据上下文,自动进行转换 在Java 5,还引入了值缓存(静态工厂方法valueOf),默认缓存范围为**-128 ~ 127** Boolean,缓存Boolean.TRUE/Boolean.FALSE Short,缓存**-128 ~ 127** Byte,数值有限,全部缓存 Character,缓存\u0000 ~ \u007F 自动装箱 + 自动拆箱12Integer integer = 1;int unboxing = integer++; 1234561: invokestatic // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;8: invokevirtual // Method java/lang/Integer.intValue:()I11: iconst_112: iadd13: invo...














