Java并发编程⾯试题
Synchronized ⽤过吗,其原理是什么?
Synchronized是jvm实现的⼀种互斥同步访问⽅式,底层是基于对象的监视器monitor实现的。
被synchronize修饰的代码在反编译后发现,在代码的开始和结束是通过monitorenter和monitorexit实现的。
当虚拟机执⾏到monitorenter时,线程会尝试获取对象的monitor锁,基于monitor锁,⼜产⽣了⼀个锁计数器的概念。
当执⾏到monitorenter时,若对象未被锁定,或当前线程已经持有该对象锁,则锁计数器+1。
当执⾏monitorexit时,该对象的锁计数器-1,当锁计数器=0时,该线程释放该对象锁权限,其他阻塞线程可以获取该对象锁权限。
你刚才提到获取对象的锁,这个“锁”到底是什么?如何确定对象的锁?
"锁"的本质其实是monitorenter和monitorexit字节码指令的⼀个Reference类型的参数,即要锁定和解锁的对象。我们知道,使⽤Synchronized可以修饰不同的对象,因此,对应的对象锁可以这么确定。
如果Synchronized明确指定了锁对象,⽐如Synchronized(变量名)、Synchronized(this)等,说明加解锁对象为该对象。
若Synchronized修饰的⽅法为⾮静态⽅法,表⽰此⽅法对应的对象为锁对象;若Synchronized修饰的⽅法为静态⽅法,则表⽰此⽅法对应的类对象为锁对象。
注意,当⼀个对象被锁住时,对象⾥⾯所有⽤Synchronized修饰的⽅法都将产⽣堵塞,⽽对象⾥⾮Synchronized修饰的⽅法可正常被调⽤,不受锁影响。
什么是可重⼊性,为什么说 Synchronized 是可重⼊锁?
可重⼊性:
  若⼀个程序或⼦程序可以“在任意时刻被中断然后操作系统调度执⾏
  另外⼀段代码,这段代码⼜调⽤了该⼦程序不会出错”,则称其为可重⼊
 (reentrant或re-entrant)的。
  简⾔之,⼀个线程持有锁时,当其他线程尝试获取该锁时,会被阻塞;
  ⽽这个线程尝试获取⾃⼰持有锁时,如果成功说明该锁是可重⼊的,反之则不可重⼊。
synchronized如何实现可重⼊性:
  synchronized关键字经过编译之后,会在同步块的前后分别形成
  monitorenter和monitorexit这两个字节码指令。每个锁对象内部维护⼀个计
  数器,该计数器初始值为0,表⽰任何线程都可以获取该锁并执⾏相应的⽅法。
  根据虚拟机规范的要求,在执⾏monitorenter指令时,⾸先要尝试获取对象的
  锁。如果这个对象没被锁定,或者当前线程已经拥有了那个对象的锁,把锁的计
  数器加1,相应的,在执⾏monitorexit指令时会将锁计数器减1,当计数器为0
  时,锁就被释放。如果获取对象锁失败,那当前线程就要阻塞等待,直到对象锁
  被另外⼀个线程释放为⽌。
VM 对 Java 的原⽣锁做了哪些优化?
⾃旋锁:
在java6之前,monitor的实现都要依赖底层操作系统的互斥锁。
由于java线程和底层操作系统的线程是映射关系,所以线程的挂起和唤醒都要
与操作系统交互,从⽤户态转到内核态执⾏,开销太⼤。
⼀种优化⽅式是使⽤⾃旋锁:由于⼤部分的共享数据锁定时间很短,为了短暂时
间去挂起和唤醒线程⾮常不划算,⾃旋锁在jdk1.4引⼊,1.6默认开启,⾃旋等
待虽然避免了线程切换的开销,但⾃旋的线程要占⽤处理器时间的,所以若锁被
占⽤的时间很短,⾃旋等待的效果就会⾮常好,反之锁被占⽤的时间很长,那么
⾃旋的线程只会⽩⽩消耗 CPU 资源。
因此⾃旋等待的时间必须要有⼀定的限度,超过限定的次数仍然没有成功获得
锁,就应当挂起(阻塞)线程了。⾃旋次数的默认值是 10 次。
⾃适应⾃旋锁:
在 JDK 1.6 中引⼊了⾃适应⾃旋锁。
⾃适应意味着⾃旋的时间不再固定了,⽽是由前⼀次在同⼀个锁上的⾃旋时间及锁的拥有者的状态来决定。
如果在同⼀个锁对象上,⾃旋等待刚刚成功获得过锁,并且持有锁的线程正在运⾏中,那么虚拟机就会认为这次⾃旋也很有可能再次成功,进⽽它将允许⾃旋等待持续相对更长的时间,⽐
如100个循环。
如果对于某个锁,⾃旋很少成功获得过,那在以后要获取这个锁时将可能省略掉⾃旋过程,以避免浪费处理器资源。
锁消除:
在动态编译同步块的时候,JIT 编译器可以借助⼀种被称为逃逸分析(Escape Analysis)的技术来判断同步块所使⽤的锁对象是否只能够被⼀个线程访问⽽没有被发布到其他线程。从⽽取
消对这部分代码的同步。
锁消除:指虚拟机即时编译器在运⾏时,对⼀些代码上要求同步,但被检测到不可能存在共享数据竞争的锁进⾏消除。主要根据逃逸分析。
锁粗化:
当 JIT 编译器发现⼀系列连续的操作都对同⼀个对象反复加锁和解锁,甚⾄加锁操作出现在循环体中的时候,会将加锁同步的范围扩散(粗化)到整个操作序列的外部。
在编写代码的时候,总是推荐将同步块的作⽤范围(锁粒度)限制得尽量⼩(只在共享数据的实际作⽤域中才进⾏同步),这样是为了使得需要同步的操作数量尽可能变⼩,如果存在锁竞
争,那等待锁的线程可以尽快的拿到锁。
锁粒度:不要锁住⼀些⽆关的代码。
锁粗化:可以⼀次执⾏完的不要多次加锁执⾏
为什么说 Synchronized 是⾮公平锁?
synchronized使⽤的是⾮公平锁,并且是不可设置的。这是因为⾮公平锁的吞吐量⼤于公平锁,并且是主流操作系统线程调度的基本选择,所以这也是synchronized使⽤⾮公平锁原由。
sql数据库查询语句怎么写
什么是锁消除和锁粗化?
锁消除:
在动态编译同步块的时候,JIT 编译器可以借助⼀种被称为逃逸分析(Escape Analysis)的技术来判断同步块所使⽤的锁对象是否只能够被⼀个线程访问⽽没有被发布到其他线程。从⽽取消对这部分代码的同步。锁消除:指虚拟机即时编译器在运⾏时,对⼀些代码上要求同步,但被检测到不可能存在共享数据竞争的锁进⾏消除。主要根据逃逸分析。
锁粗化:
当 JIT 编译器发现⼀系列连续的操作都对同⼀个对象反复加锁和解锁,甚⾄加锁操作出现在循环体中的时候,会将加锁同步的范围扩散(粗化)到整个操作序列的外部。
在编写代码的时候,总是推荐将同步块的作⽤范围(锁粒度)限制得尽量⼩(只在共享数据的实际作⽤域中才进⾏同步),这样是为了使得需要同步的操作数量尽可能变⼩,如果存在锁竞争,那等待锁的线程可以尽快锁粒度:不要锁住⼀些⽆关的代码。
锁粗化⽬的:可以⼀次执⾏完的不要多次加锁执⾏
为什么说 Synchronized 是⼀个悲观锁?乐观锁的实现原理⼜是什么?什么是 CAS,它有什么特性?
Synchronized显然是⼀个悲观锁,因为它的并发策略是悲观的:不管是否会产⽣竞争,任何的数据操作都必须要加锁、⽤户态核⼼态转换、维护锁计数器和检查是否有被阻塞的线程需要被
唤醒等操作。
乐观锁原理:先进⾏操作,如果没有其他线程征⽤数据,那操作就成功了;如果共享数据有征⽤,产⽣了冲突,那就再进⾏其他的补偿措施。
这种乐观的并发策略的许多实现不需要线程挂起,所以被称为⾮阻塞同步。
乐观锁的核⼼算法是CAS(CompareandSwap,⽐较并交换),它涉及到三个操作数:内存值、预期值、新值。当且仅当预期值和内存值相等时才将内存值修改为新值。这样处理的逻辑
是,
⾸先检查某块内存的值是否跟之前我读取时的⼀样,如不⼀样则表⽰期间此内存值已经被别的线程更改过,舍弃本次操作,否则说明期间没有其他线程对此内存值操作,可以把新值设置给
此块内存。
urrent中的AtomicInteger底层也是根据CAS算法实现的。
那么CAS底层是根据什么实现的呢?
CAS->调⽤Native⽅法通过C++ -> 通过汇编语⾔local cmpex命令实现。简⽽⾔之,CAS最跟本还是通过cpu的互斥锁实现
乐观锁⼀定就是好的吗?
不⼀定。乐观锁是相对于悲观锁⽽⾔的,是预测共享数据不会发⽣改变,适合于
读多写少的场景,如果是写多的场景可能会在验证预测值和内存实际值时由于数
据被频繁更改⽽造成该线程不停处于“获取内存值->改变值->判断值->获取内存值”的循环之中。
跟 Synchronized 相⽐,可重⼊锁 ReentrantLock 其实现原理有什么不同?
可重⼊性:
  从名字上理解,ReenTrantLock的字⾯意思就是再进⼊的锁,其实synchronized关键字所使⽤的锁也是可重⼊的,两者关于这个的区别不⼤。两者都是同⼀个线程没进⼊⼀次,锁的计数器都⾃增1,
  所以要等到锁的计数器下降为0时才能释放锁。
锁的实现:
  Synchronized是依赖于JVM实现的,⽽ReenTrantLock是JDK实现的,有什么区别,说⽩了就类似于操作系统来控制实现和⽤户⾃⼰敲代码实现的区别。前者的实现是⽐较难见到的,后者有直接的源码可供阅读。
性能的区别:
  在Synchronized优化以前,synchronized的性能是⽐ReenTrantLock差很多的,但是⾃从Synchronized引⼊了偏向锁,轻量级锁(⾃旋锁)后,两者的性能就差不多了,在两种⽅法都可⽤的情况下,
  官⽅甚⾄建议使⽤synchronized,其实synchronized的优化我感觉就借鉴了ReenTrantLock中的CAS技术。都是试图在⽤户态就把加锁问题解决,避免进⼊内核态的线程阻塞。
功能区别:
便利性:很明显Synchronized的使⽤⽐较⽅便简洁,并且由编译器去保证锁的加锁和释放,⽽ReenTrantLock需要⼿⼯声明来加锁和释放锁,为了避免忘记⼿⼯释放锁造成死锁,所以最好在finally中声明释放锁。
锁的细粒度和灵活度:很明显ReenTrantLock优于Synchronized
ReenTrantLock独有的能⼒:
1.ReenTrantLock可以指定是公平锁还是⾮公平锁。⽽synchronized只能是⾮公平锁。所谓的公平锁就是先等待的线程先获得锁。
2.ReenTrantLock提供了⼀个Condition(条件)类,⽤来实现分组唤醒需要唤醒的线程们,⽽不是像synchronized要么随机唤醒⼀个线程要么唤醒全部线程。
3.ReenTrantLock提供了⼀种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。
ReenTrantLock实现的原理:
简单来说,ReenTrantLock的实现是⼀种⾃旋锁,通过循环调⽤CAS操作来实现加锁。它的性能⽐较好也是因为避免了使线程进⼊内核态的阻塞状态。
  想尽办法避免线程进⼊内核的阻塞状态是我们去分析和理解锁设计的关键钥匙。
什么情况下使⽤ReenTrantLock:
如果你需要实现ReenTrantLock的三个独有功能时。
那么请谈谈 AQS 框架是怎么回事⼉?
AQS(AbstractQueuedSynchronizer类)是⼀个⽤来构建锁和同步器的框
架,各种Lock包中的锁(常⽤的有ReentrantLock、ReadWriteLock),以
及其他如 Semaphorex CountDownLatch,甚⾄是早期的 FutureTask 等,都
是基于AQS来构建。
1、AQS在内部定义了⼀个volatile int state变量,表⽰同步状态:当线程调⽤lock⽅法时,如果state=O,说明没有任何线程占有共享资源的锁,可以获得锁并将state=1;如果state=1,
 则说明有线程⽬前正在使⽤共享变量,其他线程必须加⼊同步队列进⾏等待。
2、AQS通过Node内部类构成的⼀个双向链表结构的同步队列,来完成线程获取锁的排队⼯作,当有线程获取锁失败后,就被添加到队列末尾。
3、Node类是对要访问同步代码的线程的封装,包含了线程本⾝及其状态叫waitStatus(有五种不同取
值,分别表駅是否被阻塞,是否等待唤醒,是否已经被取消等),
 每个Node结点关联其prev结点和next结点,⽅便线程释放锁后快速唤醒下⼀个在等待的线程,是⼀个FIFO的过程。
1、Node类有两个常量,SHARED和EXCLUSIVE,分别代表共享模式和独占模式。所谓共享模式是⼀个锁允许多条线程同时操作(信号量 Semaphore就是基于AQS的共享模式实现的),
    独占模式是同⼀个时间段只能有⼀个线程对共享资源进⾏操作,多余的请求线程需要排队等待(如 ReentranLock)。
2、AQS通过内部类ConditionObject构建等待队列(可有多个),当Condition调⽤wait。⽅法后,线程将会加⼊等待队列中,⽽当 Condition调⽤signal。⽅法后,
    线程将从等待队列转移动同步队列中进⾏锁竞争。
4、AQS和Condition各⾃维护了不同的队列,在使⽤Lock和Condition 的时候,其实就是两个队列的互相移动。
请尽可能详尽地对⽐下 Synchronized 和 ReentrantLock 的异同。
同上
ReentrantLock 是如何实现可重⼊性的?
ReentrantLock在内部使⽤了内部类Sync来管理锁,所以真正的获取锁是由Sync的实现类控制的。Sync有两个实现,分别为NonfairSync(⾮公平锁)和FairSync(公平锁)。
Sync通过继承AQS实现,在AQS中维护了⼀个private volatile int state来计数重⼊次数,避免了频繁的持有释放操作带来效率问题。
ReentrantLock内部⾃定义了同步器Sync(Sync既实现了 AQS,⼜实现了 AOS,⽽AOS提供了⼀种互斥锁持有的⽅式),其实就是加锁的时候通过 CAS算法,将线程对象放到⼀个双向链表
中,每次获取锁的时候,看下当前维护的那个线程ID和当前请求的线程ID是否⼀样,⼀样就可重⼊了。
ReentrantLock源码
abstract static class Sync extends AbstractQueuedSynchronizer {
...
}
// ReentrantLock默认是⾮公平锁
public ReentrantLock() {
sync = new NonfairSync();
}
// 可以通过向构造⽅法中传true来实现公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
线程抢锁过程(公平锁):
protected final boolean tryAcquire(int acquires) {
/
/ 当前想要获取锁的线程
final Thread current = Thread.currentThread();
// 当前锁的状态
int c = getState();
// state == 0 此时此刻没有线程持有锁
if (c == 0) {
// 虽然此时此刻锁是可以⽤的,但是这是公平锁,既然是公平,就得讲究先来后到,
// 看看有没有别⼈在队列中等了半天了
if (!hasQueuedPredecessors() &&
// 如果没有线程在等待,那就⽤CAS尝试⼀下,成功了就获取到锁了,
// 不成功的话,只能说明⼀个问题,就在刚刚⼏乎同⼀时刻有个线程抢先了 =_=
/
/ 因为刚刚还没⼈的,我判断过了
compareAndSetState(0, acquires)) {
// 到这⾥就是获取到锁了,标记⼀下,告诉⼤家,现在是我占⽤了锁
setExclusiveOwnerThread(current);
return true;
}
}
// 会进⼊这个else if分⽀,说明是重⼊了,需要操作:state=state+1
// 这⾥不存在并发问题
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 如果到这⾥,说明前⾯的if和else if都没有返回true,说明没有获取到锁
return false;
}
除了 ReetrantLock,你还接触过 JUC 中的哪些并发⼯具?
通常所说的并发包(JUC)也就是urrent及其⼦包,集中了Java并发的各种基础⼯具类,具体主要包括⼏个⽅⾯:
  1、提供了 CountDownLatch、CyclicBarrier、Semaphore等,⽐ Synchronized更加⾼级,可以实现更加丰富多线程操作的同步结构。
  2、提供了 ConcurrentHashMap、有序的 ConcunrrentSkipListMap,或者通过类似快照机制实现线程安全的动态数组CopyOnWriteArrayList等各种线
程安全的容器。
  3、提供了 ArrayBlockingQueue、SynchorousQueue 或针对特定场景的 PriorityBlockingQueue等,各种并发队列实现。
  4、强⼤的Executor框架,可以创建各种不同类型的线程池,调度任务运⾏等。
*请谈谈 ReadWriteLock 和 StampedLock
虽然ReentrantLock和Synchronized简单实⽤,但是⾏为上有⼀定局限性,要么不占,要么独占。实际应⽤场景中,有时候不需要⼤量竞争的写操作,⽽是以并发读取为主,为了进⼀步优化并发操作的粒度,
Java提供了读写锁。读写锁基于的原理是多个读操作不需要互斥,如果读锁试图锁定时,写锁是被某
个线程持有,读锁将⽆法获得,⽽只好等待对⽅操作结束,这样就可以⾃动保证不会读取到有争议的数据。ReadWriteLock代表了⼀对锁,下⾯是⼀个基于读写锁实现的数据结构,当数据量较⼤,并发读多、并发写少的时候,能够⽐纯同步版本凸显出优势。
读写锁看起来⽐Synchronized的粒度似乎细⼀些,但在实际应⽤中,其表现也并不尽如⼈意,主要还是因为相对⽐较⼤的开销。所以,JDK在后期引⼊了 StampedLock,在提供类似读写锁的同时,还⽀持优化读模式优化读基于假设,⼤多数情况下读操作并不会和写操作冲突,其逻辑是先试着修改,然后通过validate⽅法确认是否进⼊了写模式,如果没有进⼊,就成功避免了开销;如果进⼊,则尝试获取读锁。
如何让 Java 的线程彼此同步?你了解过哪些同步器?请分别介绍下
JUC中的同步器三个主要的成员:CountDownLatch、CyclicBarrier和 Semaphore,通过它们可以⽅便地实现很多线程之间协作的功能。
CountDownLatch叫倒计数,允许⼀个或多个线程等待某些操作完成。
CyclicBarrier叫循环栅栏,它实现让⼀组线程等待⾄某个状态之后再全部同时执⾏,⽽且当所有等待线程被释放后,CyclicBarrier可以被重复使⽤。CyclicBarrier的典型应⽤场景是⽤来等待并发线程结束。
CyclicBarrier 的主要⽅法是await(),await()每被调⽤⼀次,计数便会减少1,并阻塞住当前线程。当计数减⾄0时,阻塞解除,所有在此CyclicBarrier上⾯阻塞的线程开始运⾏。
在这之后,如果再次调⽤await。,计数就⼜会变成N-1,新⼀轮重新开始,这便是Cyclic的含义所在。CyclicBarrier.await。带有返回值,⽤来表⽰当前线程是第⼏个到达这个Barrier的线程。
Semaphore, Java版本的信号量实现,⽤于控制同时访问的线程个数,来达到限制通⽤资源访问的⽬的,其原理是通过acquire。获取⼀个许可,如果没有就等待,⽽release。释放⼀个许可。
如果Semaphore的数值被初始化为1,那么⼀个线程就可以通过acquire 进⼊互斥状态,本质上和互斥锁是⾮常相似的。但是区别也⾮常明显,⽐如互斥锁是有持有者的,⽽对于Semaphore这种计数器结构,虽然有类似但其实不存在真正意义的持有者,除⾮我们进⾏扩展包装。tablelayout布局
CountDownLatch实例代码:
package com.atguigu.thread;
import urrent.CountDownLatch;
/**
*
*  *让⼀些线程阻塞直到另⼀些线程完成⼀系列操作后才被唤醒。
*
* CountDownLatch主要有两个⽅法,当⼀个或多个线程调⽤await⽅法时,这些线程会阻塞。
* 其它线程调⽤countDown⽅法会将计数器减1(调⽤countDown⽅法的线程不会阻塞),
* 当计数器的值变为0时,因await⽅法阻塞的线程会被唤醒,继续执⾏。
*
* 解释:6个同学陆续离开教室后值班同学才可以关门。
*
* main主线程必须要等前⾯6个线程完成全部⼯作后,⾃⼰才能开⼲
*/
public class CountDownLatchDemo
{
public static void main(String[] args) throws InterruptedException
{
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <=6; i++) //6个上⾃习的同学,各⾃离开教室的时间不⼀致
{
new Thread(() -> {
System.out.println(Thread.currentThread().getName()+"\t 号同学离开教室");
}, String.valueOf(i)).start();
}
countDownLatch.await();
System.out.println(Thread.currentThread().getName()+"\t****** 班长关门⾛⼈,main线程是班长");
}
}
View Code
CyslicBarrier实例代码:
package com.atguigu.thread;
import urrent.BrokenBarrierException;
import urrent.CyclicBarrier;
/**
*
*
* CyclicBarrier
* 的字⾯意思是可循环(Cyclic)使⽤的屏障(Barrier)。它要做的事情是,
* 让⼀组线程到达⼀个屏障(也可以叫同步点)时被阻塞,
* 直到最后⼀个线程到达屏障时,屏障才会开门,所有
* 被屏障拦截的线程才会继续⼲活。
* 线程进⼊屏障通过CyclicBarrier的await()⽅法。
*
* 集齐7颗龙珠就可以召唤神龙
*/
public class CyclicBarrierDemo
{
private static final int NUMBER = 7;
public static void main(String[] args)
{
//CyclicBarrier(int parties, Runnable barrierAction)
CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, ()->{System.out.println("*****集齐7颗龙珠就可以召唤神龙");}) ;
for (int i = 1; i <= 7; i++) {
new Thread(() -> {
try {
System.out.println(Thread.currentThread().getName()+"\t 星龙珠被收集 ");
cyclicBarrier.await();
} catch (InterruptedException | BrokenBarrierException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}, String.valueOf(i)).start();
}
}
}
treemap用法
View Code
Semphore实例代码:
package com.atguigu.thread;
import java.util.Random;
import urrent.Semaphore;
import urrent.TimeUnit;
/**
*
* @Description: TODO(这⾥⽤⼀句话描述这个类的作⽤)
*
* 在信号量上我们定义两种操作:
* acquire(获取)当⼀个线程调⽤acquire操作时,它要么通过成功获取信号量(信号量减1),
*            要么⼀直等下去,直到有线程释放信号量,或超时。
32的8位二进制原码是* release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。
*
* 信号量主要⽤于两个⽬的,⼀个是⽤于多个共享资源的互斥使⽤,另⼀个⽤于并发线程数的控制。
*/
public class SemaphoreDemo
{
public static void main(String[] args)
{
Semaphore semaphore = new Semaphore(3);//模拟3个停车位
for (int i = 1; i <=6; i++) //模拟6部汽车
{
new Thread(() -> {
try
{
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"\t 抢到了车位");
TimeUnit.SECONDS.sleep(new Random().nextInt(5));
java经典上机编程题System.out.println(Thread.currentThread().getName()+"\t------- 离开");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
}
}, String.valueOf(i)).start();
}
}
}
View Code
*CyclicBarrier 和 CountDownLatch 看起来很相似,请对⽐下呢?
它们的⾏为有⼀定相似度,区别主要在于:
  1、CountDownLatch是不可以重置的,所以⽆法重⽤,CyclicBarrier没有这种限制,可以重⽤。
  2、CountDownLatch的基本操作组合是countDown/await,调⽤await的线程阻塞等待countDown⾜够的次数,不管你是在⼀个线程还是多个线程⾥countDown,只要次数⾜够即可。
  CyclicBarrier的基本操作组合就是 await,当所有的伙伴都调⽤了 await,才会继续进⾏任务,并⾃动进⾏重置。
CountDownLatch⽬的是让⼀个线程等待其他N个线程达到某个条件后,⾃⼰再去做某个事(通过CyclicBarrier的第⼆个构造⽅法public CyclicBarrier(int parties, Runnable barrierAction),
在新线程⾥做事可以达到同样的效果)。⽽CyclicBarrier的⽬的是让N多线程互相等待直到所有的都达到某个状态,然后这N个线程再继续执⾏各⾃后续(通过CountDownLatch在某些场合也能完成类似的效果)。Java 中的线程池是如何实现的?
1、⼀个任务提交到线程池,⾸先判断核⼼线程数是否满,如果核⼼线程数未满,则创建⼀个线程执⾏任务,否则进⼊下⼀步骤
2、判断阻塞队列是否满,如果未满则将任务放⼊阻塞队列,否则执⾏下⼀步骤
3、判断线程池中线程数是否达到最⼤线程数,如果未达到则创建新线程执⾏任务,否则启动拒绝策略。
创建线程池的⼏个核⼼构造参数?
int corePoolSize:核⼼线程数
int maximumPoolSize:最⼤线程数
long keepAliveTime:线程存活时间
压力容器abcd分类
TimeUnit unit:线程存活时间单位
BlockingQueue<Runnable> workQueue:阻塞队列
ThreadFactory threadFactory:线程⼯⼚
RejectedExecutionHandler handler:任务拒绝策略
线程池中的线程是怎么创建的?是⼀开始就随着线程池的启动创建好的吗?
不是。线程池默认初始化后不启动Worker,等待有请求时才启动。
既然提到可以通过配置不同参数创建出不同的线程池,那么 Java 中默认实现好的线程池⼜有哪些呢?请⽐较它们的异同
1、SingleThreadExecutor 线程池
  这个线程池只有⼀个核⼼线程在⼯作,也就是相当于单线程串⾏执⾏所有任务。如果这个唯⼀的线程因为异常结束,那么会有⼀个新的线程来替代它。此线程池保证所有任务的执⾏顺序按照任务的提交顺序执⾏2、FixedThreadPool 线程池
  FixedThreadPool是固定⼤⼩的线程池,只有核⼼线程。每次提交⼀个任务就创建⼀个线程,直到线程达到线程池的最⼤⼤⼩。线程池的⼤⼩⼀旦达到最⼤值就会保持不变,如果某个线程因为执⾏异常⽽结束,  那么线程池会补充⼀个新线程。
  FixedThreadPool多数针对⼀些很稳定很固定的正规并发线程,多⽤于服务器。
3、CachedThreadPool线程池
4、ScheduledThreadPool 线程池
ScheduledThreadPool :核⼼线程池固定,⼤⼩⽆限的线程池。此线程池⽀持定时以及周期性执⾏任
务的需求。创建⼀个周期性执⾏任务的线程池。如果闲置⾮核⼼线程池会在DEFAULT_KEEPALIVEMILLIS时间内回如何在 Java 线程池中提交线程?
1、execute: ute ⽅法接收⼀个线程实例,它⽤来执⾏⼀个任务:ut(Runnable runable)
2、submit: ExecutorService.submit。⽅法返回的是 Future 对象。可以⽤ isDone()来查询Future是否已经完成,当任务完成时,它具有⼀个结果, 可以调⽤get。来获取结果。
也可以不⽤isDone。进⾏检查就直接调⽤ get(),在这种情况下,get()将阻塞,直⾄结果准备就绪。
什么是 Java 的内存模型,Java 中各个线程是怎么彼此看到对⽅的变量的?
Java的内存模型定义了程序中各个变量的访问规则,即在虚拟机中将变量存储到内存和从内存中取出这样的底层细节。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。