-
AQS通过Node内部类构成的一个双向链表结构的同步队列,来完成线程获取锁的排队工作,当有线程获取锁失败后,就被添加到队列末尾。
●Node类是对要访问同步代码的线程的封装,包含了线程本身及其状态叫waitStatus(有五种不同取值,分别表示是否被阻塞,是否等待唤醒,是已经被取消等),每个Node结点关联其prev结点和next结点,方便线程释放锁后快速唤醒下一个在等待的线程,是一 个FIFO的过程。
●Node类有两个常量,SHARED 和EXCLUSIVE,分别代表共享模式和独占模式。所谓共享模式是一个锁允许多条线程同时操作(信号量Semaphore就是基于AQS的共享模式实现的),独占模式是同一个时间段只能有一个线程对共享资源进行操作,多余的请求线程需要排队等间段只能有一个线程对共享资源进行操作,多余的请求线程需要排队等待(如ReentranLock)。
-
AQS通过内部类ConditionObject构建等待队列(可有多个),当Condition调用wait()方法后,线程将会加入等待队列中,而当Condition调用signal()方法后,线程将从等待队列转移动同步队列中进行锁竞争。
-
AQS和Condition各自维护了不同的队列,在使用Lock和Condition的时候,其实就是两个队列的互相移动。
3、请尽可能详尽地对比下Synchronized和ReentrantLock的异同。
ReentrantLock是Lock的实现类,是一个互斥的同步锁.从功能角度,ReentrantL ock比Synchronized 的同步操作更精细(因为可以像普通对象一样使用),甚至实现Synchronized没有的高级功能,如:
●等待可中断:当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待,对处理执行时间非常长的同步块很有用。
●带超时的获取锁尝试:在指定的时间范围内获取锁,如果时间到了仍然无法获取则返回。
●可以判断是否有线程在排队等待获取锁。
●可以响应中断请求:与Synchronized不同,当获取到锁的线程被中断时,能够响应中断,中断异常将会被抛出,同时锁会被释放。
●可以实现公平锁。
ReentrantL ock内部自定义了同步器Sync(Sync既实现了AQS,而AOS提供了一种互斥锁持有的方式),其实就是加锁的时候通过CAS算法,将线程对象放到一个双向链表中,每次获取锁的时候,看下当前维护的那个线程ID和当前请求的线程ID 是否一样,一样就可重入了。
5、除了ReetrantLock,你还接触过JUC中的哪些并发工具?
通常所说的并发包(JUC)也就是java.util.concurrent及其子包,集中了Java并发的各种基础工具类,具体主要包括几个方面:
●提供了CountDownL atch、CyliBarrier. Semaphore等, 比
Synchronized更加高级,可以实现更加丰富多线程操作的同步结构。
●提供了ConcurrentHashMap、有序的ConcunrrentSkipListMap,或者通过类似快照机制实现线程安全的动态数组CopyOnWriteArrayList 等各种线程安全的容器。
●提供了ArrayBlockingQueue、SynchorousQueue 或针对特定场景的PriorityBlockingQueue等,各种并发队列实现。
●强大的Executor 框架,可以创建各种不同类型的线程池,调度任务运行等。
6、请谈谈ReadWriteLock和StampedLock.
虽然Reentrantl ock和Synchronized简单实用,但是行为上有一定局限性,要么不占,要么独占。实际应用场景中,有时候不需要大量竞争的写操作,而是以并发读取为主,为了进一步优化并发操作的粒度, Java 提供了读写锁。读写锁基于的原理是多个读操作不需要互斥,如果读锁试图锁定时,写锁是被某个线程持有,读锁将无法获得,而只好等待对方操作结束,这样就可以自动保证不会读取到有争议的数据。
ReadWriteLock代表了一对锁,下面是一个基于读写锁实现的数据结构,当数据量较大,并发读多、并发写少的时候,能够比纯同步版本凸显出优势:
读写锁看起来比Synchronized的粒度似乎细一些,但在实际应用中,其表现也并不尽如入意,主要还是因为相对比较大的开销。所以,JDK 在后期引入了StampedL ock,在提供类似读写锁的同时,还支持优化读模式。
优化读基于假设,大多数情况下读操作并不会和写操作冲突,其逻辑是先试着修改,然后通过validate方法确认是否进入了写模式,如果没有进入,就成功避免了开销;如果进入,则尝试获取读锁。
7、CyclicBarrier 和CountDownLatch看起来很相似,请对比下呢?
它们的行为有一定相似度,区别主要在于:
●CountDownLatch是不可以重置的,所以无法重用,CyclicBarrier 没有这种限制,可以重用。
●CountDownLatch的基本操作组合是countDown/await,调用await的线程阻塞等待countDown足够的次数,不管你是在一个线程还是多个线程里countDown,只要次数足够即可。CyclicBarrier 的基本操作组合就是await,当所有的伙伴都调用了await,才会继续进行任务,并自动进行重置。
CountDownL atch目的是让一个线程等待其他N个线程达到某个条件后,自己再去做某个事(通过CydlicBarrier的第二个构造方法publicCylicBarrier(int parties, Runnable barrierAction),在新线程里做事可以达到同样的效果)。而CyllicBarrier的目的是让N多线程互相等待直到所有的都达到某个状态,然后这N个线程再继续执行各自后续(通过CountDownL atch在某些场合也能完成类似的效果)。
●在Java中,所谓的线程池中的“线程”,其实是被抽象为了一个静态内部类Worker,它基于AQS实现,存放在线程池的HashSet workers成员变量中;
●而需要执行的任务则存放在成员变量workQueue(BlockingQueueworkQueue)中。
这样,整个线程池实现的基本思想就是:从workQueue中不断取出需要执行的任务,放在Workers中进行处理。
Java中的线程池的创建其实非常灵活,我们可以通过配置不同的参数,创建出行为不同的线程池,这几个参数包括:
●corePoolSize:线程池的核心线程数。
●maximumPoolSize:线程池允许的最大线程数。
●keepAliveTime:超过核心线程数时闲置线程的存活时间。
●workQueue:任务执行前保存任务的队列,保存由execute方法提交的Runnable任务。
10、什么是Java的内存模型,Java中各个线程是怎么彼此看到对方的变量的?
Java的内存模型定义了程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出这样的底层细节。此处的变量包括实例字段、静态字段和构成数组对象的元素,但是不包括局部变量和方法参数,因为这些是线程私有的,不会被共享,所以不存在竞争问题。
Java中各个线程是怎么彼此看到对方的变量的呢?Java中定义了主内存与工作内存的概念:
所有的变量都存储在主内存,每条线程还有自己的工作内存,保存了被该线程使用到的变量的主内存副本拷贝。
线程对变量的所有操作(读取、赋值)都必须在工作内存中进行,不能直接读写主内存的变量。不同的线程之间也无法直接访问对方工作内存的变量,线程间变量值的传递需要通过主内存。
11、既然volatile能够保证线程间的变量可见性,是不是就意味着基于volatile变量的运算就是并发安全的?
最后
本人也收藏了一份Java面试核心知识点来应付面试,借着这次机会可以免费送给我的读者朋友们
目录:
Java面试核心知识点
一共有30个专题,足够读者朋友们应付面试啦,也节省朋友们去到处搜刮资料自己整理的时间!有需要的朋友戳这里即可免费获取
Java面试核心知识点
已经有读者朋友靠着这一份Java面试知识点指导拿到不错的offer了,各位读者朋友们快来免费获取吧
888/java-p7)**
[外链图片转存中…(img-OAz1J8Z9-1628632325299)]
Java面试核心知识点
已经有读者朋友靠着这一份Java面试知识点指导拿到不错的offer了,各位读者朋友们快来免费获取吧
[外链图片转存中…(img-pEky3neg-1628632325301)]