既上篇复习一后,继续再来了解 J.U.C 下包的相关类
1. JUC 包中的原子类是哪4类?
参考
synchronized采用的是悲观锁策略来达到线程安全的目的,这并不是特别高效的一种解决方案
在J.U.C下的atomic包提供了一系列的操作简单,性能高效,并能保证线程安全的类去更新基本类型变量,数组元素,引用类型以及更新对象中的字段类型。atomic包下的这些类都是采用的是乐观锁策略去原子更新数据,在java中则是使用CAS操作具体实现。
- 基本类型
- AtomicBoolean:以原子更新的方式更新boolean;
- AtomicInteger:以原子更新的方式更新Integer;
- AtomicLong:以原子更新的方式更新Long;
AtomicInteger:(可以阅读下相关源码)
atomicInteger借助了UnSafe提供的CAS操作能够保证数据更新的时候是线程安全的,并且由于CAS是采用乐观锁策略,因此,这种数据更新的方法也具有高效性。
- 数组类型
- AtomicIntegerArray:原子更新整型数组中的元素;
- AtomicLongArray:原子更新长整型数组中的元素;
- AtomicReferenceArray:原子更新引用类型数组中的元素
- 引用类型
- AtomicReference:原子更新引用类型;
- AtomicReferenceFieldUpdater:原子更新引用类型里的字段;
- AtomicMarkableReference:原子更新带有标记位的引用类型;
- 字段类型
- AtomicIntegeFieldUpdater:原子更新整型字段类;
- AtomicLongFieldUpdater:原子更新长整型字段类;
- AtomicStampedReference:原子更新引用类型,这种更新方式会带有版本号。在更新的时候会带有版本号,是为了解决CAS的ABA问题;
2. 什么是AQS?AQS原理是什么?
(基于队列同步器AbstractQueueSychronier(简称同步器) ,在说队列同步器时,我提到了两大点:队列同步器维护一个同步队列,是双向链表,同时也维护一个同步状态state;还说到它的方法分类:独占式锁和分享式锁。
说说独占式锁的实现:我说到了最重要的一点就是加入同步队列的时结点需要自旋查看它的前一个结点是否获取锁,如果获取的话,它应该尝试获取锁(tryAcquire))
AQS的全称为(AbstractQueuedSynchronizer),这个类在java.util.concurrent.locks包下面。
AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用队列锁实现的,即将暂时获取不到锁的线程加入到队列中。
原理:
同步器是用来构建锁和其他同步组件的基础框架,它的实现主要依赖一个int成员变量state来表示同步状态以及通过一个FIFO队列构成等待队列。它的子类必须重写AQS的几个protected修饰的用来改变同步状态的方法,其他方法主要是实现了排队和阻塞机制。状态的更新使用getState,setState以及compareAndSetState这三个方法。
AQS底层使用了模板方法模式
AQS定义两种资源共享方式:
Exclusive(独占):只有一个线程能执行,如ReentrantLock。又可分为公平锁和非公平锁:
**Share(共享):**多个线程可同时执行,如Semaphore/CountDownLatch。
Semaphore(信号量): 允许多个线程同时访问
synchronized 和 ReentrantLock 都是一次只允许一个线程访问某个资源,Semaphore(信号量)可以指定多个线程同时访问某个资源。
CountDownLatch (倒计时器,一个线程等待):
用来控制一个或者多个线程等待多个线程,直到其他线程的操作执行完后再执行。
维护了一个计数器 cnt,每次调用 countDown() 方法会让计数器的值减 1,减到 0 的时候,那些因为调用 await() 方法而在等待的线程就会被唤醒。
CyclicBarrier(循环栅栏,一组线程等待):
用来控制多个线程互相等待,只有当多个线程都到达时,这些线程才会继续执行。
和 CountdownLatch 相似,都是通过维护计数器来实现的。线程执行 await() 方法之后计数器会减 1,并进行等待,直到计数器为 0,所有调用 await() 方法而在等待的线程才能继续执行。
CountDownLatch与CyclicBarrier的比较:
- CountDownLatch一般用于某个线程A等待若干个其他线程执行完任务之后,它才执行;而CyclicBarrier一般用于一组线程互相等待至某个状态,然后这一组线程再同时执行;CountDownLatch强调一个线程等多个线程完成某件事情。CyclicBarrier是多个线程互等,等大家都完成,再携手共进。
- 调用CountDownLatch的countDown方法后,当前线程并不会阻塞,会继续往下执行;而调用CyclicBarrier的await方法,会阻塞当前线程,直到CyclicBarrier指定的线程全部都到达了指定点的时候,才能继续往下执行;
- CountDownLatch方法比较少,操作比较简单,而CyclicBarrier提供的方法更多,比如能够通过getNumberWaiting(),isBroken()这些方法获取当前多个线程的状态,并且CyclicBarrier的构造方法可以传入barrierAction,指定当所有线程都到达时执行的业务功能;
- CountDownLatch是不能复用的,而CyclicLatch是可以复用的。
读写锁 ReentrantReadWriteLock 可以保证多个线程可以同时读,所以在读操作远大于写操作的时候,读写锁就非常有用了,相对ReentrantLock。
3. ConcurrentHashMap是什么,实现?
ConcurrentHashMap就是线程安全的map,其中利用了锁分段的思想提高了并发度。
在JDK 1.7版本中:
- ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。
- Segment实现了ReentrantLock,所以Segment是一种可重入锁,扮演锁的角色.HashEntry用于存储键值对数据。
在JDK 1.8版本中:
- 取消了Segment分段锁,采用CAS和synchronized来保证并发安全。
- 数据结构跟HashMap1.8的结构类似,数组+链表/红黑二叉树.Java 8在链表长度超过一定阈值(8)时将链表(寻址时间复杂度为O(N))转换为红黑树(寻址时间复杂度为O(log(N)))
ConcurrentHashMap和Hashtable的区别
1.7版本与1.8版本的ConcurrentHashMap的实现对比
4. CopyOnWriteArrayList是什么,作用?实现?
CopyOnWriteArrayList 读取是完全不用加锁的,并且更厉害的是:写入也不会阻塞读取操作。只有写入和写入之间需要进行同步等待。这样一来,读操作的性能就会大幅度提升。
CopyOnWriteArrayList 类的所有可变操作(add,set等等)都是通过创建底层数组的新副本来实现的。当 List 需要被修改的时候,我并不修改原有内容,而是对原有数据进行一次复制,将修改的内容写入副本。写完之后,再将修改完的副本替换原来的数据,这样就可以保证写操作不会影响读操作了。
5. ConcurrentLinkedQueue 是什么?
Java提供的线程安全的 Queue 可以分为阻塞队列和非阻塞队列,ConcurrentLinkedQueue 非阻塞队列的一种实现,阻塞队列可以通过加锁来实现,非阻塞队列可以通过 CAS 操作实现。
ConcurrentLinkedQueue 主要使用 CAS 非阻塞算法来实现线程安全。
ConcurrentLinkedQueue 适合在对性能要求相对较高,同时对队列的读写存在多个线程同时进行的场景,即如果对队列加锁的成本较高则适合使用无锁的ConcurrentLinkedQueue来替代。
6. BlockingQueue
阻塞队列(BlockingQueue)被广泛使用在“生产者-消费者”问题中,其原因是BlockingQueue提供了可阻塞的插入和移除的方法。当队列容器已满,生产者线程会被阻塞,直到队列未满;当队列容器为空时,消费者线程会被阻塞,直至队列非空时为止。
相关几个实现类:
- ArrayBlockingQueue:数组实现的有界阻塞队列,该队列命令元素FIFO(先进先出)
- LinkedBlockingQueue:LinkedBlockingQueue是用链表实现的有界阻塞队列,同样满足FIFO的特性与ArrayBlockingQueue 相比起来具有更高的吞吐量。
- PriorityBlockingQueue:一个支持优先级的无界阻塞队列。默认情况下元素采用自然顺序进行排序,也可以通过自定义类实现 compareTo() 方法来指定元素排序规则,或者初始化时通过构造器参数 Comparator 来指定排序规则。
7. JMM 是什么,三大特性?
Java 内存模型试图屏蔽各种硬件和操作系统的内存访问差异,以实现让 Java 程序在各种平台下都能达到一致的内存访问效果。
三大特性:
- 原子性:原子性是指一个操作是不可中断的,要么全部执行成功要么全部执行失败
- 可见性:可见性指当一个线程修改了共享变量的值,其它线程能够立即得知这个修改
- 有序性:为了性能优化,编译器和处理器会进行指令重排序;也就是说java有序性可以总结为:如果在本线程内观察,所有的操作都是有序的;如果在一个线程观察另一个线程,所有的操作都是无序的。
指令重排:在 Java 内存模型中,允许编译器和处理器对指令进行重排序,重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。
参考文献:
- 《Java并发编程的艺术》
- Java 并发专题27篇
- CyC2018/Java并发
- JavaGuide/Java并发