线程和进程?
进程是资源分配和调度的最小独立单元,线程是CPU调度的基本单元;
一个进程可以包含多个线程,多个线程共享该进程的资源;
线程可以看作是轻量级的进程;
进程间通信的方式?
volatile,synchronized,wait/notifyAll,管道输入/输出流,thread.join(),ThreadLocal
聊聊synchronized和lock?
synchronized是Java中的一个关键字,通过该关键字修饰来达到同步的效果,被synchronized修饰的代码块会隐式的加锁解锁,对开发者都是透明的,底层是基于进入和退出Monitor对象来实现的同步,会在代码块前后形成monitorenter和monitorexit两条指令,分别进行加锁解锁(可重入锁);
进入时执行monitorenter获取锁计算器+1,退出时执行monitorexit计数器-1;
lock则是一个对象锁,开发者需要显式的获取锁释放锁,常见的有ReentrantLock,lock对象锁是一种支持可中断的锁,而synchronized则不支持,lock在发生异常时如果没有主动的去释放锁会导致死锁;synchronized会自动释放异常线程占有的锁,不会发生死锁;
lock可以获取锁的状态,synchronized不可以;
wait和sleep的区别?
wait和sleep都是针对线程的,都可以达到让线程等待或者说暂时性停止的效果,但是这两个方法又是不同的,主要体现在下面几点:
如何指定多个线程的执行顺序?
设定一个tempNumber用来指定当前获得执行机会的线程编号,每个线程执行完就更新这个number,指明下一个要有执行权利的线程,同时唤醒所有等待的线程;
每个线程启动之后,写一个while判断tempNumber是否等于自己的number值,如果不是则wait,否则就执行线程;
多线程产生死锁的4个必要原因?
互斥条件:一个资源每次只能被一个线程使用;
请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放,就例如AB线程各自持有对方的锁,却需要等待对方释放锁才能释放自己的锁;
不剥夺条件:线程已经获得了资源,在没有结束之前,不允许其他线程获得资源,也就是线程对共享资源的独占性;
循环等待条件:多个线程之间形成环形等待的情况,即多个线程陷入死循环;
如何避免死锁?
指定线程获取锁的顺序即可避免死锁,同一时刻只能有且仅有唯一一个线程获得锁的机会;
聊聊你对volatile的认识?
该关键字可以保证变量的可见性,但是不保证原子性;
被volatile修饰的变量,当线程处理时,会将线程本地内存设置为无效,强制要求线程去主内存操作变量;
被volatile修饰的变量在操作时会通过生成lock指令来形成内存屏障,禁止JVM进行指令重排序操作;
谈谈你对ThreadLocal的理解?
当使用 ThreadLocal 维护变量时,为每个使用该变量的线程提供了一份独立的变量副本,所以每一个线程都可以独立的操作自己的副本,而不会影响其他线程对应的副本;
ThreadLocal内部实现机制:每个线程内部都维护一个ThreadLocalMap,每个ThreadLocalMap又包含若干个Entry,Entry 的 Key 是一个 ThreadLocal 实例,Value 是一个线程特有对象。Entry 的作用是为所属线程建立起一个 ThreadLocal 实例与一个线程特有对象之间的对应关系;
聊聊CountDownLatch和CyclicBarrier?
作用:用于监听某些初始化操作,等待初始化执行完毕,通知主线程继续工作。允许一个或多个线程等待其他线程完成操作;
怎么做:CountDownLatch是通过一个计数器来实现的,计数器的初始值为线程的数量。每当一个线程完成了自己的任务后,计数器的值就会减1。当计数器值到达0时,它表示所有的线程已经完成了任务,然后在闭锁上等待的线程就可以恢复执行任务;
CountDownLatch的await()方法,会使线程等待,直到计数器为0,结束等待
CountDownLatch的countDown()计数器 -1
CyclicBarrier 的字面意思是可循环使用(Cyclic)的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续执行;
CountDownLatch的计数器只能使用一次。而CyclicBarrier的计数器可以使用reset() 方法重置。所以CyclicBarrier能处理更为复杂的业务场景,比如如果计算发生错误,可以重置计数器,并让线程们重新执行一次;
聊聊Semaphore?
信号量是用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用公共资源
聊聊Exchanger?
Exchanger主要是用于线程之间进行数据交换的,提供一个同步点,在这个同步点,两个线程可以交换彼此的数据;两个线程通过Exchanger方法来进行交换,当第一个线程先执行Exchanger()方法,他会一直等待第二个线程也执行Exchanger方法,然后两个线程交换数据;
聊聊Java实现原子操作的CAS?
CAS:又称无锁算法,全称Compare-and-Swap(比较并交换),依赖处理器的汇编指令cmpxchg(比较交换)来完成,当CAS指令执行时,当且仅当内存位置V符合旧预期值时A时,处理器才会用新值B去更新V的值,否则就不执行更新,但是无论是否更新V,都会返回V的旧值,该操作过程就是一个原子操作;
什么是CAS的BAB问题,如何解决?
使用CAS时因为会先去检查内存位置的旧值A有没有发生变化,发生变化则更新最新值B,但是存在一种情况就是,初次读取内存旧值时是A,再次检查之前这段期间,如果内存位置的值发生过从A变成B再变回A的过程,我们就会错误的检查到旧值还是A,认为没有发生变化,其实已经发生过A-B-A得变化;
ABA解决方案:使用版本号,即1A-2B-3A,这样就会发现1A到3A的变化,不存在ABA变化无感知问题,JDK的atomic包中提供一个带有标记的原子引用类AtomicStampedReference来解决ABA问题,它可以通过控制变量值得版本号来保证CAS的正确性。该类的compareAndSet方法会首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果都相等则以原子方式该引用和该标志的值进行更新;
聊聊队列同步器AQS?
队列同步器是用来构建锁或者其他同步组件的基础元素,主要是使用一个int成员变量来表示同步状态,通过一个FIFO队列来完成资源的获取线程的排队工作,队列同步器是面向锁的实现的;
同步器依赖内部的同步队列(FIFO)来完成同步状态的管理,过程如下:当前线程获取同步状态失败时,同步器就会将当前线程以及等待状态的信息构成一个节点并加入到同步队列中,同时阻塞当前线程,当同步状态释放时,会将首节点的线程唤醒,再次尝试获取同步状态;
聊聊你所熟悉的并发容器有哪些?
ConcurrentHashMap:线程安全高效的HashMap,内部采用锁分段技术来优化,每个段其实就是一个小的HashTable,他们有自己各自的锁,只要多个修改操作发生在不同的段上,他们之间就可以并发的进行;
ConcurrentLinkedQueue:一个基于链表节点实现的无界限安全队列(不允许Null元素存在,有null存在则链表就会断了),该队列的元素遵循先进先出(FIFO)原则,它采用了无等待算法(CAS)来实现,不会发生阻塞,适用于高并发场景;
BlockingQueue:支持阻塞的插入和移除操作的队列,这是一个接口,具体有下面几种实现类:
-
ArrayBlockingQueue :由数组结构组成的有界阻塞队列;
-
LinkedBlockingQueue :由链表结构组成的有界阻塞队列;
-
PriorityBlockingQueue :支持优先级排序的无界阻塞队列;
-
DelayQueue:使用优先级队列实现的无界阻塞队列;
-
SynchronousQueue:不存储元素的阻塞队列;
-
LinkedTransferQueue:链表结构组成的无界阻塞队列;
-
LinkedBlockingDeque:链表结构组成的双向阻塞队列;
Java提供的线程池有哪几种?
线程池在JDK的JUC包下有个线程池工厂类Executors,通过这个工厂类我们可以创建多种不同的线程池实例,主要有下面4种:
-
newFixedThreadPool(corePoolSize):该方法返回一个固定数量的线程池,线程数量始终不变,当有任务提交时,若线程池中有空闲线程,则立即执行,若没有,则会被缓存在一个任务队列中等待有空闲的线程再去执行;(核心线程数等于最大线程数,默认空闲时间为0,空闲立马销毁);
-
newSingleThreadExecutor():创建一个线程数量始终为1的线程池,若空闲则执行,否则入队列等待被执行;(核心线程数量为1,最大线程数量也为1,空闲等待时间为0);
-
newCachedThreadPool():返回一个可根据实际情况调整线程个数的线程池,不限制最大线程数量,若有任务则没线程时则创建线程,每个线程空闲等待时间为60秒,60秒后如果该线程没有任务可执行,则被回收;(核心线程数量为0,最大线程数量为最大,空闲等待时间为60s);
-
newScheduledThreadPool(corePoolSize):返回一个ScheduledExecutorService对象,该线程池可以指定线程数量,他调了父类super的方法里面还是一个ThreadPoolExecutor,这种方式创建的线程池中每个线程都有定时器的功能;
线程池的工作原理是什么?
我们创建一个线程池之后可以通过submit方法向线程池提交任务,线程池内部维护的工作者线程的数量就是该线程池的线程池大小,有 3 种形态:
新任务提交给线程池时:首先判断线程池中是否有空闲线程,如果有则直接使用线程;如果没有则判断线程池当前线程数是否小于核心线程池,小于则创建新的线程,否则加入到等待队列中排队;当等待队列满时,则判断总线程数是否超过最大线程池大小,如果没有则创建新的线程,如果有则采取拒绝策略;
JDK提供的线程池的拒绝策略有哪些?
-
AbortPolicy策略:该策略直接抛出异常,阻止系统工作;
-
CallerRunsPolicy策略:只要线程池未关闭,该策略直接在调用者线程中运行当前被丢弃的任务。显然这样不会真的丢弃任务,但是,调用者线程性能可能急剧下降;
-
DiscardOledestPolicy策略:丢弃最老的一个请求任务,并尝试再次提交当前任务;
-
DiscardPolicy策略:默默的丢弃无法处理的任务,不予任何处理,当做什么都没发生的样子;
如何自定义线程池?
public ThreadPoolExecutor(int corePoolSize, //线程池核心线程数量
int maximumPoolSize, //最大线程数量
long keepAliveTime, //线程空闲等待时间
TimeUnit unit, //线程空闲等待时间的单位
BlockingQueue<Runnable> workQueue, //存放待执行任务的队列即等待队列
RejectedExecutionHandler handler ) { //拒绝任务的处理策略
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
线程的生命周期中,状态是如何转移的?