【问答】Java多线程

2020-03-02  本文已影响0人  星冉子

线程的状态,画一个线程的生命周期状态图

线程状态:NEW,未启动的线程;RUNNABLE,运行中,包括就绪状态和运行中状态,调用start方法并获取到锁后进入就绪状态,获取到CPU后进入运行中状态;BLOCKED,受阻塞并等待监视器锁,被某个锁(synchronizers)給block住了;WATING,无限期等待某个condition或monitor发生,一般停留在park, wait, sleep,  join(未设置超时时间)等语句里;TIMED_WATING,有时限的等待另一个线程的特定操作,如sleep,加上超时时间的wait;TERMINATED,已退出的;

线程生命周期:不同于线程状态,有点类似,新建、就绪、运行、阻塞(包括同步阻塞,等待阻塞)、死亡;

线程的阻塞的方式

线程阻塞指的是暂停一个线程的执行以等待某个条件发生(不一定是阻塞状态),包括sleep超时等待、suspend挂起线程、yield让出时间片、join等待另一线程执行完、wait释放锁等待被唤醒、synchronized同步阻塞、LockSupport.park同步阻塞、Lock.lock自旋阻塞

线程如何退出结束

使用标志位正常退出;使用interrupt方法中断线程;使用stop方法强行终止,该方法已作废;

如何解决并发问题

只有存在共享数据时才需要考虑线程安全问题,尽量消除共享变量,做到线程间不共享,或者设置为不可变对象;使用同步锁机制,synchronized、lock;使用无锁并发,Atomic、volatile、CAS;使用线程安全的类,并发集合等;

Thread的 notify()给notifyAll()的区别?

调用对象的wait方法后会释放锁并进入该对象的等待区等待,不参与锁竞争;调用notify方法后会随机唤醒一个等待区等待的线程进入对象的进入区等待,和其他线程一起竞争锁,重新获取到锁后再继续往下执行;调用notifyAll方法后会唤醒所有等待区等待的线程进入对象的进入区等待,和其他线程一起竞争锁,只有重新获取到锁的线程才会继续执行;

讲讲java同步机制的wait和notify

wait/notify方法不是线程的机制,而是Object提供的方法,调用native方法实现;wait/notify方法的执行必须放到synchronized代码块中,即需要先获取到锁,因为wait/notify方法是基于对象的,因此需要一个数据结构来存放等待的线程,而且这个数据结构应当是与该对象绑定的;wait方法会释放锁,否则其他线程就获取不到锁来执行notify方法;notify方法不会释放锁,只是将线程放入对象的进入区等待,只有当线程退出synchronized块后才会释放;问题:丢失的信号:notify再wait之前了,解决:notify时通过共享变量保存下来,wait时判断变量;假唤醒:没有notify而从wait醒了,解决:自旋wait;

notifiy()是唤醒的那一个线程?

notify会随机唤醒对象等待区等待的一个线程,选择哪个线程取决于操作系统对多线程管理的实现

Thread.sleep()唤醒以后是否需要重新竞争?

sleep不会释放锁,会让出CPU时间片,所以唤醒后不会重新竞争锁,只是会重新等待CPU时间片;

问一个Thread.join()相关的问题?

Join用于主线程等待子线程执行完成后再执行,即A线程中调用B.join,B线程执行完后A线程才继续执行;内部实现是通过synchronized+wait实现的,join是Thread的方法,使用synchronized修饰,所以A线程执行B.join时需要先获取B线程对象的锁,这样join方法内部才能使用循环(线程alive则一直wait)wait等待在B线程对象上,采用循环wait是为了防止过程中被唤醒,join的调用只是普通方法调用,B线程执行完成后内部会notify等待的A线程;

如何解决死锁?

死锁是由多个线程竞争同一资源,而资源的请求和释放顺序不当导致的;产生的条件包括互斥(资源只能由一个线程获得)、占用且等待(一个线程持有一个资源又请求新的资源,而新资源被其他线程持有)、不可抢占、循环等待;

避免死锁就是破坏上诉的几个条件如避免一个线程获取多把锁、避免一个线程在一把锁内获取多个资源,每把锁只占用一个资源、使用定时锁代替内部锁机制lock.tryLock()、一次性获取所有资源; 

sleep和wait的区别

sleep方法是Thread类的静态方法,可以在任何地方使用,需要抛异常,使程序暂停执行指定的时间,让出CPU,但不会释放锁,时间到了之后进入就绪状态,不需要被唤醒;wait方法是Object超类的成员方法,会释放锁,进入此对象的等待区等待,不需要抛异常,只能在同步方法和同步代码块中使用,需要被唤醒;

sleep和sleep(0)的区别

sleep(0),如果线程调度器的可运行队列中有大于或等于当前线程优先级的就绪线程存在,则会让出时间片调度其他线程,如果没有则当前线程会继续执行,就像没调用一样;sleep(timeout),会引发线程上下文切换,让出时间片,约等于(取决于系统时间精度)timeout时间后再继续等待获取时间片执行;

java有哪些锁?用过reentrantlock吗?reentrantlock与synmchronized的区别

Java锁从不同角度可以分为公平锁/非公平锁、可重入锁、互斥锁/读写锁、乐观锁(CAS)/悲观锁(Synchronized)、分段锁(一种锁的设计思想)、偏向锁/轻量级锁/重量级锁(针对Synchronized的锁的不同状态)、自旋锁(不阻塞,循环获取锁)、类锁/对象锁; 

区别:Synchronized:关键字,内置特性,基于对象头MarkWord+monitor对象实现,自动释放锁(执行完、异常、Waiting),可重入,悲观锁,无法响应中断,非公平锁;缺点:不能灵活控制,影响执行效率,如IO阻塞要一直等;同一资源不能并发读;不能知道线程是否成功获取锁;Lock:类,非内置特性,基于AQS+LockSupport+CAS+自旋实现,需要手动释放锁,可重入,可响应中断,默认非公平锁,可公平,可解决Synchronized的缺点;性能:竞争不激烈时Synchronized更优,激烈时Synchronized下降几十倍,Lock常态更优;

ThreadLocal的使用场景

ThreadLocal是线程本地变量,为每个使用该变量的线程提供独立的变量副本,不会影响其它线程的使用;内部主要有4个方法set/get/remove/initialValue,线程隔离的原理在于ThreadLocal类中有个ThreadLocalMap静态内部类,Thread中有ThreadLocalMap成员变量来保存ThreadLocal的值; 

使用场景:每个线程需要有自己单独的实例,实例需要在多个方法中共享,但不希望被多线程共享;线程间数据隔离;获取数据库连接、Session等场景、解决线程安全问题,如Spring内部对有状态的bean采用ThreadLocal解决线程安全问题;

为什么线程执行要调用start而不是直接run

rrun方法只是普通方法,调用后还是顺序执行;start方法才是启动线程执行,调用后线程会被放到等待队列进入就绪状态,等待CPU调度,并不会马上执行,获取到时间片后再调用run方法执行,底层实现是先判断线程状态是否正常,再调用本地方法start0实现;

volatile的用途?volatile关键字用法?volatile的原理,作用,能代替锁么?

作用:轻量级的synchronized,使用和执行成本更低,不会引起上下文切换和调度;2个作用:内存可见性(对变量的修改直接刷到主存、对共享变量的修改对所有线程可见即线程能获取到最新值)、禁止指令重排序;不保证原子性(单个volatile变量的读/写具有原子性,但类似于volatile++这种复合操作不具有原子性);  

实现原理:happens-before原则(对volatile变量的写操作 happen-before 后续的读操作);volatile变量在字节码级别没有任何区别,在汇编级别使用了lock指令前缀、内存屏障实现(将当前处理器缓存行数据写回内存;一个处理器的缓存回写会使其他缓存了该内存地址的数据无效;缓存一致性协议:每个处理器通过嗅探总线数据来保证数据一致;);需要多个线程区写同一个共享变量,volatile变量是不合适的;  

使用场景:状态位、单线程写多线程读、和synchronized结合实现低开销的读写锁;和synchronized区别:仅能用于变量,不保证原子性(sync可以),不会阻塞,编译器不会优化;

atomic与 volatile的区别?

volatile保证一个线程对变量的修改对其他线程可见,无法保证原子性,当只有一个线程修改共享变量时,适合使用volatile;Atomic相对于volatile语义更强,通过如getAndIncrement()的方法保证了原子性,底层采用volatile+UnSafe的CAS方法实现

可重入的读写锁,可重入是如何实现的?

ReentrantReadWriteLock是基于AQS实现的,而AQS继承至AOS,AOS用于保存当前获取到锁的线程对象,当线程竞争锁时,先判断AQS中的state变量是否为0,若为0则直接获取锁,否则判断AOS中当前获取到锁的线程是否是当前线程,若是则state加1,当前线程获取到锁,否则自旋获取锁,释放锁时state减1直到为0,从而通过state实现可重入;

可重入锁中对应的wait和notify

ReentrantLock提供newCondition方法创建一个和当前锁对象关联的condition对象,condition对象的await、signal、signalAll方法类似于wait、notify、notifyAll方法

Java线程池中基于缓存和基于定长的两种线程池,当请求太多时分别是如何处理的?定长的有界的队列,如果队列也满了呢?交换进磁盘?基于缓存的线程池解决方法呢?

基于缓存的线程池CachedThreadPool,最大线程数为int.Max,新来的请求会使用缓存的线程执行,若没有缓存线程则新启线程执行,执行完后线程会缓存1分钟之后再销毁;基于定长的线程池FixedThreadPool,使用无界队列LinkedBlockingQueue作为队列,过多的请求会放入队列,当线程数小于固定线程数时再启动新线程处理,执行完后线程立即被销毁

新任务先按核心线程数启动,满之后放入队列,队列满之后按最大线程数启动,若最大线程数也满了则按照设置的拒绝策略处理,包括抛异常、原线程中运行、丢弃队列中的最后一个任务执行、不处理丢弃(默认);

synchronized加在方法上用的什么锁

若加在普通方法上则锁当前对象实例,若加载静态方法上则锁类;

可重入锁中的lock和trylock的区别

ReentrantLock获取锁定与三种方式:a)  lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程阻塞等待,直到获得锁;   b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;  c) tryLock(timeout), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;  d) lockInterruptibly,如果获取了锁定立即返回,如果没有获取锁定,当前线程阻塞等待,直到获得锁定,或者当前线程被别的线程中断

线程池的原理,为什么要创建线程池?

使用线程池的好处:降低资源消耗:重复利用线程,降低创建和销毁的消耗;提高响应速度:任务到达时可马上执行,不需要等待线程创建;提高线程的可管理型:统一分配、调优、监控线程资源;

ThreadPoolExecutor提交新任务时的处理流程:1. 判断核心线程池是否已满,否则创建线程执行任务,是则进入下一步;(即使核心线程池有空闲线程,如果未满也会创建新线程)2. 判断任务队列是否已满,否则将任务加入队列,是则进入下一步;(若使用无界队列则此步无意义,始终未满)3. 判断整个线程池是否已满,否则创建线程执行任务,是则按照饱和策略处理;(可能先添加的任务在队列中排队,而后面的任务先执行)提交到线程池的任务会封装成工作线程,循环获取任务执行; 

创建线程池有哪几个核心参数?如何合理配置线程池的大小?

创建线程池的参数:corePoolSize:核心线程数量;runnableTaskQueue:任务队列,阻塞队列;maximumPoolSize:线程池最大数量;包含核心线程数;ThreadFactory:线程创建工厂,可统一命名;rejectedExecutionHandler:队列和线程池都满时的处理策略,包括抛异常、原线程中运行、丢弃队列中的最后一个任务执行、不处理丢弃;keepAliveTime:工作线程空闲后的存活时间;

考虑已下方面:1. 任务性质:CPU密集(配置少量线程Cpu个数+1)、IO密集(设置多数线程2*Cpu个数)、混合型任务;2. 任务优先级:高中低,可使用优先队列处理;3. 任务执行时间:长中短,可由不同规模的线程池分开处理,或者使用优先队列,时间短的优先;4. 任务依赖性:如系统资源或数据库连接,应该设置多的线程,提高CPU利用率; 

多线程的几种实现方式,什么是线程安全。

实现方式:  继承Thread类,重写run方法; 实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target; 通过Callable和FutureTask创建线程;通过线程池创建线程;

线程安全:当多个线程访问某个方法时,不管通过怎样的调用方式或者这些线程如何交替的执行,我们在主程序中不需要去做任何的同步,这个类的结果行为都是我们设想的正确行为,那么我们就可以说这个类是线程安全的;

Synchronized的原理是什么,一般用在什么地方(比如加在静态方法和非静态方法的区别,静态方法和非静态方法同时执行的时候会有影响吗),解释以下名词:重排序,自旋锁,偏向锁,轻量级锁?

使用方式:方法锁、对象锁synchronized(this)、类锁synchronized(Demo.Class);方法锁包括普通方法加锁(实例锁)、静态方法加锁(类锁)、代码块加锁(锁指定对象);

实现原理:锁的存储:存储在Java对象头中的mark word中;锁对象:每个Object对象实现都有一个native C++实现的Monitor对象,JVM基于进入和退出monitor对象实现锁,进入前代码经编译后插入monitorenter指令,退出时插入monitorexit指令,monitorenter和monitorexit是配对的;

锁的状态:无锁、偏向锁、轻量级锁、重量级锁,锁只能升级不能降级,是为了提高获得和释放锁的效率;偏向锁:引入目的:通常锁由一个线程多次获得,为了减少这种情况下的获得代价;实现原理:Java对象头的Mark Wod中存储了偏向锁的线程ID和锁状态,存在竞争时才释放锁或者竞争锁;加锁:线程进入同步块时,将线程ID记录到对象的头中,下次进入只需要检测头信息即可重新获得,没有信息才使用CAS竞争锁;解锁:退出同步代码块或者有线程竞争时;关闭:Java默认开启了偏向锁,如果确定程序大多数都处于竞争,可以通过JVM参数关闭偏向锁,这时会进入轻量级锁;轻量级锁:加锁:线程将对象的MarkWord复制到线程的栈空间并修改然后写回对象,失败时通过自旋获取锁;解锁:写回对象的MarkWord,若失败表示锁存在竞争,锁升级为重量级锁,重量级锁时其他线程都是阻塞;   

重排序:编译器或处理器为了优化程序性能而对指令序列重排序的手段;从源代码到最终的指令序列经过了3个重排序:编译器优化重排序(Java编译器处理)、指令集并行重排序、内存系统重排序(处理器处理);如何保证内存可见性:通过禁止特定类型的重排序保证可见性和正确性,编译器通过编译规则禁止,处理器通过插入内存屏障处理;内存屏障指令分为4类:LoadLoad、LoadStore、StoreStore、StoreLoad

用过哪些原子类,他们的原理是什么

原子类框架:原子类:AtomicInteger、AtomicReference等;原子数组:AtomicIntegerArray、AtomicReferenceArray等;优化原子类:Long/DoubleAdder、Long/DoubleAccumulator;

实现原理:数据结构:AtomicInteger有3个成员变量:Unsafe unsafe(Sun提供的CAS和获取对象内存地址的工具类)、long valueOffset(记录value的内存地址)、volatile int value(value值,可见性,但没有原子性),各方法调用unsafe的方法实现;原理总结:通过CAS乐观锁保证原子性,通过自旋保证修改最终成功,volatile保证可见性,通过降低锁粒度(LongAdder、LongAccumulator的优化实现,通过将value值分成数组,实现多段锁)解决高并发时的CAS性能问题; 

线程池的关闭方式有几种,各自的区别是什么

shutdown:将线程池的状态设置为SHUTWDOWN状态,正在执行的任务会继续执行下去,没有被执行的则中断;

shutdownNow:将线程池的状态设置为STOP,正在执行的任务则被中断,没被执行任务的则返回;  

假如有一个第三方接口,有很多个线程去调用获取数据,现在规定每秒钟最多有10个线程同 时调用它,如何做到

可以通过ScheduledThreadPool+Semaphore实现,Semaphore初始化10个信号量,启动一个定时线程通过scheduleAtFixedRate方法每隔0.5秒释放5个信号量,工作线程获取信号量才能执行,执行完毕后不释放信号量;

用三个线程按顺序循环打印abc三个字母,比如abcabcabc

方法一:synchronized+flag;方法二:Lock+3个Condition+flag;方法三:3个Semaphore,初始化为1,0,0;注意是循环,所以join不行;

ThreadLocal用过么,用途是什么,原理是什么,用的时候要注意什么

使用:线程局部变量,它为每一个使用该变量的线程都提供一个变量值的副本,每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突;4个方法:set、get、remove、initialValue;

实现原理:ThreadLocal包含ThreadLocalMap静态内部类;ThreadLocalMap包含Entry静态内部类保存数据,继承WeakReference,Key为ThreadLocal,Value为真实值;Thread中包含ThreadLocal.ThreadLocalMap对象,是ThreadLocal起作用的根源;ThreadLocal.set:先获取当前线程,再获取线程的ThreadLocalMap,在put(ThreadLocal,value);ThreadLocal.get:先获取当前线程,再获取线程的ThreadLocalMap,再根据ThreadLocal获取Entry,再获取Entry的value返回;

ThreadLocal内存泄漏:原因:ThreadLocal在ThreadLocalMap中通过弱引用被Entry中的Key引用,如果ThreadLocal被回收且线程生命周期很长(如线程池中线程不会被销毁)Entry中的Value不会回收,造成内存泄漏。解决:ThreadLocal的get()、set()、remove()方法调用的时候会清除掉线程ThreadLocalMap中所有Entry中Key为null的Value,但程序如果不调用就不会自动清除;最好的实践是使用完ThreadLocal后手动调用remove()方法删除。

如果让你实现一个并发安全的链表,你会怎么做

方法一:参照Collections.synchronizedList的实现,对线程不安全的List的方法使用synchronized或者Lock包装一层;方法二:参照ConcurrentLinkedQueue的实现,对成员变量和Node节点中的成员变量使用voliate修饰保证可见性,使用UnSafe的CAS方法保证原子性;

有哪些无锁数据结构,他们实现的原理是什么

无锁算法(Lock-Free)通过非阻塞算法保护共享数据结构,非阻塞算法保证为共享资源竞争的线程不会通过互斥让它们的执行无限期暂停,一般通过CAS(通过CPU原子指令CMPXCHG实现)实现; 

无锁数据结构:AtomicXX、ConcurrentSkipListMap、ConcurrentSkipListSet、ConcurrentLinkedQueue、ConcurrentLinkedDeque,其他基于AQS实现的数据结构都使用LockSupport.park来阻塞线程,LockSupport底层也是用互斥锁来实现的; 

Atomic实现原理:通过CAS乐观锁保证原子性,通过自旋保证修改最终成功,volatile保证可见性,通过降低锁粒度(LongAdder、LongAccumulator的优化实现,通过将value值分成数组,实现多段锁)解决高并发时的CAS性能问题;  

ConcurrentSkipListMap实现原理:基于跳表实现,通过空间来换取时间,建立多级索引,实现以二分查找遍历一个有序链表,时间复杂度等同于红黑树,但实现更简单;通过CAS+Volatile保证线程安全; 

ConcurrentLinkedQueue、ConcurrentLinkedDeque:非阻塞队列,基于链表,无界,通过volatile+CAS(UnSafe+自旋)实现线程安全;

CAS机制是什么,如何解决ABA问题

CAS:无锁操作,乐观锁,假设操作数据时没有冲突,有时通过比较交换解决;3个参数:内存地址的实际值、旧值、新值;当且仅当旧值和内存值相同时,将内存值修改为新值,否则什么都不做;可以通过版本号解决ABA问题,如AtomicStampedReference通过加版本号解决,AtomicMarkableReference通过标记是否更改过来解决;

多线程如果线程挂住了怎么办

线程挂起的原因sleep、join、wait(使用notify唤醒)、suspend(使用resume恢复),如果是锁阻塞导致的线程挂起,可以使用jstack导出线程堆栈分析具体的原因;

countdowlatch和cyclicbarrier的内部原理和用法,以及相互之间的差别

CountDownLatch:倒数计数器,构造时设定计数值,工作线程执行完后执行countdown让计数值减1,主线程await,当计数值归零后,所有阻塞线程恢复执行;原理:内部实现静态内部类Sync继承至AQS,构造时就是设置AQS的state字段,countdown方法就是让state减1,await就是CAS等待state值变为0; 

CyclicBarrier:循环栅栏,构造时设定等待线程数,还可设置最后一个到达的线程额外执行的任务,调用一次await则减1达到栅栏,当所有线程都到达栅栏后,栅栏放行;原理:没有实现AQS,通过ReentrantLock和Condition实现同步,构造时用2个变量保存线程数,1个final不变,用于reset时重用,1个await时减1;处理异常:当等不到所有线程时抛出异常(线程中断、超时、调用了重置方法reset);

和CountDownLatch区别:CountDownLatch只能用一次,CyclicBarrier可调用reset重置来重复使用;可获取阻塞的线程数; 

countdownlatch的await方法是怎么实现的

await方法直接调用了AQS的acquireSharedInterruptibly,首先尝试获取共享锁getState()==0,若获取不到(state!=0),则自旋等待,创建一个共享节点加入AQS的同步队列,直到获取到锁,将自己设置为head节点并通知其他等待的线程;

对AbstractQueuedSynchronizer了解多少,讲讲加锁和解锁的流程,独占锁和公平所加锁有什么不同

设计思想:构造同步器的通用机制和基础组件;同步器3大关键操作:同步器的状态变更(当前是否允许其他线程获取锁,通过volatile int保存,通过compareAndSetState原子更改)、线程阻塞和释放(通过LockSupport.park/unpark实现)、插入和移出队列(包括多个线程的阻塞等待FIFO队列即同步队列,只有1个;和条件队列,可以有多个,双向链表实现);  

数据结构:volatile int state(同步状态,0未占用,1占用,>1占用,同一线程重入次数)、volatile Node head(同步队列头结点)、volatile Node tail(同步队列尾节点)、Node(同步队列节点)、ConditionObject(条件队列);方法:compareAndSetState(CAS设置同步状态)、钩子方法(未实现,独占式获取tryAcquire/Release、共享式获取tryAcquire/ReleaseShared)、模板方法(独占式获取acquire/release、共享式获取acquire/releaseShared);Node:waitStatus(等待状态)、Node prev(前驱节点)、Node next(后继节点)、Thread thread(获取同步状态的线程)、Node nextWaiter(条件队列的后继节点);ConditionObject:Node firstWaiter(条件队列首节点)、Node lastWaiter(条件队列尾节点); 

同步队列:队列拥有头结点和尾节点,头结点是获取到同步状态(即锁)的节点,头节点释放锁后会通知后继节点,并设为新的头节点;加入队列的过程要保证线程安全,提供了CAS方法来设置;独占式获取同步和释放:获取状态失败的线程加入同步队列并自旋,一直到前驱节点为头结点则获取到状态则退出自旋并移除队列;释放同步时会唤醒头结点的后继节点;共享式获取同步和释放:独占式访问资源时,其他独占和共享式访问都不被允许;共享式访问时允许其他共享,不允许独占访问;共享式即同一时刻可以有多个线程获取到同步状态; 

独占式时state同步状态在0和1之间切换,返回true/false,获取同步状态之后,直接返回结束流程;共享式时返回state数值,state<0获取失败,state=0获取成功,其他线程无法获取,不需要通知后继节点;state>0获取成功,需要通知后继节点,让其他线程也尝试获取同步状态;  

使用synchronized修饰静态方法和非静态方法有什么区别

synchronized修饰static获取到的是类锁,非static方法获取到的是对象锁,他们之间不会产生互斥,如果想要让类下的所有方法共用一把锁可以用Lock实现

简述ConcurrentLinkedQueue和LinkedBlockingQueue的用处和不同之处

ConcurrentLinkedQueue:非阻塞队列,基于链表,无界,通过volatile+CAS实现线程安全;多用于消息队列; 

LinkedBlockingQueue:阻塞队列,基于单向链表,近似有界(默认Int最大值);实现原理:内部创建2个ReentrantLock和2个Condition(一个Lock一个,队列空和队列满),takeLock控制出队,putLock用于入队,因此出队和入队可以并发;插入元素:putLock加锁、判断队列是否满,满则在队列满Condtion.await,不满则直接入队、再根据队列元素个数唤醒1个入队(队列未满)或出队线程(初始为0);多用于任务队列;

怎么检测一个线程是否拥有锁

java.lang.Thread中有一个方法叫holdsLock(),它返回true如果当且仅当当前线程拥有某个具体对象的锁

用过读写锁吗,原理是什么,一般在什么场景下用

读写锁可以共享读,只允许一个写,用于读远远大于写的场景,否则由于需要额外的维护读锁的状态,性能可能不如互斥锁;核心是由一个基于AQS的同步器Sync构成,然后由其扩展出ReadLock和WriteLock组成,支持公平性选择、可重入、可降级; 

读锁获取:先根据state低16位判断是否有写锁并且获取写锁的线程是否自己,若有写锁且不是自己则获取失败;再判断state高16位的读锁是否达到最大值65535,若是则失败;然后使用CAS修改state高16位加1获取读锁,重复自旋上述操作;

写锁获取:如果读锁数量不为0或者写锁数量不为0,并且不是重入操作,则获取失败;如果当前锁的数量为0,也就是不存在操作1的情况,那么该线程是有资格获取到写锁,因此修改状态,设置独占线程为当前线程;

公平下的Sync实现策略是所有获取的读锁或者写锁的线程都需要入队排队,按照顺序依次去尝试获取锁;非公平下由于抢占式获取锁,写锁是可能产生饥饿,因此解决办法就是提高写锁的优先级,写锁可以直接参与竞争;  

开启多个线程,如果保证顺序执行,有哪几种实现方式,或者如何保证多个线程都执行完 再拿到结果

join、wait、线程池Fix(1)、Lock+Condition、CountDownLatch、CyclicBarrier、Semaphore

延迟队列的实现方式,delayQueue和时间轮算法的异同

DelayQueue是一种特殊无界阻塞队列,通过ReentrantLock实现线程安全,使用PriorityBlockingQueue保存队列,队列元素需实现Delayed接口,获取元素时需判断元素是否到期;

时间轮是Linux内核定时器的实现方法和基础之一,通过槽位+圈数实现,最重要的是两个指针,一个是触发任务的函数指针,另外一个是触发的总第几圈数,时间轮可以用简单的数组或者是环形链表来实现,DelayQueue由于涉及到排序,需要调堆,插入和移除的复杂度是O(lgn),而时间轮在插入和移除的复杂度都是O(1);

上一篇下一篇

猜你喜欢

热点阅读