一篇博客带你轻松应对java面试中的多线程与高并发

前端之家收集整理的这篇文章主要介绍了一篇博客带你轻松应对java面试中的多线程与高并发前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

1. Java线程的创建方式

(1)@H_403_7@继承thread@H_403_7@类

thread@H_403_7@类本质是实现了runnable@H_403_7@接口的一个实例,代表线程的一个实例。启动线程的方式start@H_403_7@方法start@H_403_7@是一个本地方法,执行后,执行run@H_403_7@方法代码

 

 

 

(2)@H_403_7@实现runnable@H_403_7@接口

@H_403_7@如果自己的类已经继承了别的类,就不能继承thread@H_403_7@类。只能实现runnable@H_403_7@接口。

 

 

 

(3)@H_403_7@实现callable@H_403_7@接口

@H_403_7@有返回值的任务必须实现callable@H_403_7@接口,无返回值的任务必须实现runnable@H_403_7@接口。执行callable@H_403_7@接口后,可以获取一个future@H_403_7@对象,通过future@H_403_7@对象的get@H_403_7@方法可以获得返回值。结合线程池可以实现有返回值的多线程。

 

 

 

(4)基于线程池的方式

 

 

 

2. 介绍一下java的线程池

java@H_403_7@里面线程池的顶级接口是executor@H_403_7@。严格意义上讲。executor@H_403_7@只是一个接口,真正的线程池是executorservice@H_403_7@。

@H_403_7@(1@H_403_7@)newCachedThreadPool

@H_403_7@创建一个可根据需要创建新线程的线程池,但是在以前构造的线程可用时将重用它们。对于执行很多短期异步任务的程序而言,这些线程池通常可提高程序性能调用 execute @H_403_7@将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 @H_403_7@秒钟未被使用的线程。因此,长时间保持空闲的线程池不会使用任何资源。

@H_403_7@(2@H_403_7@)newFixedThreadPool

@H_403_7@创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数 nThreads @H_403_7@线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。

@H_403_7@(3@H_403_7@)newScheduledThreadPool

创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

 

 

 

@H_403_7@(4@H_403_7@)newSingleThreadExecutor

Executors.newSingleThreadExecutor()@H_403_7@返回一个线程池(这个线程池只有一个线程),@H_403_7@这个线程

@H_403_7@池可以在线程死后(或发生异常时)重新启动一个线程来替代原来的线程继续执行下去!

3. 线程的声明周期

@H_403_7@线程的生命周期包括新建new@H_403_7@,就绪runable@H_403_7@,运行running@H_403_7@,阻塞blocked@H_403_7@和死亡dead@H_403_7@。

(1)新建状态

@H_403_7@当程序使用new@H_403_7@关键字创建了一个线程之后,该线程就属于新建状态,此时仅由jvm@H_403_7@为其分配内存,并初始化成员变量的值。

(2)就绪状态

@H_403_7@当线程对象调用了start@H_403_7@方法之后。线程处于就绪状态,jvm@H_403_7@会为其创建方法调用栈和程序计数器。此时的现场等待cpu@H_403_7@的调度。一旦拿到cpu@H_403_7@就可以立即执行。

(3)运行状态

@H_403_7@处于就绪状态的线程获得了cpu@H_403_7@的执行权,状态就更改为running@H_403_7@。此时线程处于运行状态。

(4)阻塞状态

@H_403_7@阻塞状态是指线程因为某种原因,放弃了cpu@H_403_7@的使用权,暂时停止运行。恢复阻塞后进入就绪状态,获得cpu@H_403_7@使用权之后,才进入执行状态。

阻塞的情况分为三种:

等待阻塞

@H_403_7@运行中的线程执行wait@H_403_7@方法jvm@H_403_7@会把他放入等待队列中。

同步阻塞

@H_403_7@运行的线程获取对象的同步锁的时候,jvm@H_403_7@会把该线程放入锁池中。

其他阻塞

@H_403_7@运行中的线程执行线程的sleep@H_403_7@方法join@H_403_7@方法。或者发出io@H_403_7@请求的时候,jvm@H_403_7@把对象置为阻塞状态。当sleep@H_403_7@超时,join@H_403_7@或者io@H_403_7@完毕后,就可以拿到cpu@H_403_7@的权,继续执行。

(5)死亡状态

@H_403_7@正常结束,run@H_403_7@或call@H_403_7@的方法结束。

异常结束,出现报错

@H_403_7@调用stop@H_403_7@,调用stop@H_403_7@方法可能会产生思索。

4. 终止线程的四种方式

(1)正常执行结束

(2)@H_403_7@使用同一标志,多个线程共用一个变量,变量使用volite@H_403_7@修饰,每次把他作为标志位来进行判断。

(3)interrupt@H_403_7@结束线程

@H_403_7@当线程处于阻塞状态的时候,如果使用sleep@H_403_7@,同步锁的wait@H_403_7@方法socket@H_403_7@的receive@H_403_7@方法的时候,会使现场处于阻塞状态。当调用线程的interrupt@H_403_7@方法的时候。会抛出interruptexception@H_403_7@异常。阻塞中的那个方法抛出异常,通过代码捕获异常,然后结束执行。

@H_403_7@线程未处于阻塞状态的时候,可以使用isinterrupted@H_403_7@来进行判断,while@H_403_7@来调这个函数

 

 

 

(4)stop@H_403_7@方法终止线程

stop@H_403_7@方法强制执行,会导致现场释放他所占有的所有锁、被保护的数据可能就会出现不一致性。可能会出现很多奇怪的应用程序错误

5. sleepwait方法的区别

@H_403_7@对于sleep@H_403_7@方法,属于Thread@H_403_7@类,wait@H_403_7@方法数据object@H_403_7@类中。

sleep@H_403_7@方法导致线程的短暂执行,让出cpu@H_403_7@去执行其他线程。依然监控cpu@H_403_7@,当时间到了,立马拿到cpu@H_403_7@的执行权。

@H_403_7@调用sleep@H_403_7@方法的时候,线程不会释放锁。wait@H_403_7@方法会放弃对象锁,进入锁的等待池。此方法调用notify@H_403_7@之后,才能进入锁池,进行重新竞争。

6. startrun方法的区别

start@H_403_7@方法来启动线程,真正实现了多线程运行。无需等待run@H_403_7@方法结束。可以直接执行其他方法

@H_403_7@调用start@H_403_7@方法使线程进入就绪状态,获得cpu@H_403_7@即可运行。

run@H_403_7@方法是线程的run@H_403_7@方法执行体。

7. Java后台进程

1. @H_403_7@定义:守护线程--@H_403_7@也称@H_403_7@服务线程@H_403_7@,他是后台线程,它有一个特性,即为用户线程提供公

@H_403_7@共服务,在没有用户线程可服务时会自动离开。

2. @H_403_7@优先级:守护线程的优先级比较低,用于为系统中的其它对象和线程提供服务。

3. @H_403_7@设置:通过 setDaemon(true)@H_403_7@来设置线程为@H_403_7@守护线程@H_403_7@;将一个用户线程设置为守护线程

@H_403_7@的方式是在 @H_403_7@线程对象创建 @H_403_7@之前 @H_403_7@用线程对象的 setDaemon @H_403_7@方法

4. @H_403_7@在 Daemon @H_403_7@线程中产生的新线程也是 Daemon @H_403_7@的。

5. @H_403_7@线程则是 JVM @H_403_7@级别的,以 Tomcat @H_403_7@为例,如果你在 Web @H_403_7@应用中启动一个线程,这个线程的生命周期并不会和 Web @H_403_7@应用程序保持同步。也就是说,即使你停止了 Web @H_403_7@应用,这个线程依旧是活跃的。

6. example: @H_403_7@垃圾回收线程就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,@H_403_7@程序就不会再产生垃圾垃圾回收器也就无事可做,所以当垃圾回收线程是 JVM @H_403_7@上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。

7. @H_403_7@生命周期:守护进程(Daemon@H_403_7@)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。也就是说守护线程不依赖于终端,但是依赖于系统,与系统@H_403_7@同生共死@H_403_7@。当 JVM @H_403_7@中所有的线程都是守护线程的时候,JVM @H_403_7@就可以退出了;如果还有一个或以上的非守护线程则 JVM @H_403_7@不会退出

8. Java的锁

乐观锁

@H_403_7@乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-@H_403_7@比较-@H_403_7@写的操作。java @H_403_7@中的乐观锁基本都是通过 CAS @H_403_7@操作实现的,CAS @H_403_7@是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。

悲观锁

@H_403_7@悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会 block @H_403_7@直到拿到锁。java@H_403_7@中的悲观锁就是Synchronized,AQS@H_403_7@框架下的锁则是先尝试cas@H_403_7@乐观锁去获取锁,获取不到,才会转换为悲观锁,如 RetreenLock

自旋锁

@H_403_7@自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁

@H_403_7@的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),

@H_403_7@等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。

@H_403_7@线程自旋是需要消耗 cup @H_403_7@的,说白了就是让 cup @H_403_7@在做无用功,如果一直获取不到锁,那线程也不能一直占用 cup @H_403_7@自旋做无用功,所以需要设定一个自旋等待的最大时间。

@H_403_7@如果持有锁的线程执行的时间超过自旋等待的最大时间扔没有释放锁,就会导致其它争用锁

@H_403_7@的线程在最大等待时间内还是获取不到锁,这时争用线程会停止自旋进入阻塞状态。

自旋锁的优缺点

@H_403_7@自旋锁尽可能的减少线程的阻塞,这对于锁的竞争不激烈,且占用锁时间非常短的代码块来

@H_403_7@说性能能大幅度的提升,因为自旋的消耗会小于线程阻塞挂起再唤醒的操作的消耗,这些操作会导致线程发生两次上下文切换!

@H_403_7@但是如果锁的竞争激烈,或者持有锁的线程需要长时间占用锁执行同步块,这时候就不适合

@H_403_7@使用自旋锁了,因为自旋锁在获取锁前一直都是占用 cpu @H_403_7@做无用功,占着 XX @H_403_7@不 XX@H_403_7@,同时有大量线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗,其它需要 cup @H_403_7@的线程又不能获取cpu@H_403_7@,造成 cpu @H_403_7@的浪费。所以这种情况下我们要关闭自旋锁;

自旋锁时间阈值

@H_403_7@自旋锁的目的是为了占着 cpu @H_403_7@的资源不释放,等到获取到锁立即进行处理。但是如何去选择自旋的执行时间呢?如果自旋执行时间太长,会有大量的线程处于自旋状态占用 cpu @H_403_7@资源,进而会影响整体系统的性能。因此自旋的周期选的额外重要!

JVM @H_403_7@对于自旋周期的选择,jdk1.5 @H_403_7@这个限度是一定的写死的,在 1.6 @H_403_7@引入了适应性自旋锁,适应性自旋锁意味着自旋的时间不在是固定的了,而是由前一次在同一个锁上的自旋时间以及锁的拥有者的状态来决定,基本认为一个线程上下文切换的时间是最佳的一个时间,同时 JVM @H_403_7@还针对当前 cpu @H_403_7@的负荷情况做了较多的优化,如果平均负载小于 cpus @H_403_7@则一直自旋,如果有超过(cpus/2)@H_403_7@个线程正在自旋,则后来线程直接阻塞,如果正在自旋的线程发现 Owner @H_403_7@发生了变化则延迟自旋时间(自旋计数)或进入阻塞,如果 cpu @H_403_7@处于节电模式则停止自旋,自旋时间的最坏情况是 cpu@H_403_7@的存储延迟(cpu A @H_403_7@存储了一个数据,到 cpu B @H_403_7@得知这个数据直接的时间差),自旋时会适当放弃线程优先级之间的差异。

 

 

 

Synchronized @H_403_7@同步锁

synchronized @H_403_7@它可以把任意一个非 NULL @H_403_7@的对象当作锁。他属于独占式的悲观锁,同时属于可重入锁。

Synchronized 作用范围

1. @H_403_7@作用于方法时,锁住的是对象的实例(this)@H_403_7@;

2. @H_403_7@当作用于静态方法时,锁住的是Class@H_403_7@实例,又因为Class@H_403_7@的相关数据存储在永久带PermGen@H_403_7@(jdk1.8 @H_403_7@则是 Metaspace@H_403_7@),永久带是全局共享的,因此静态方法锁相当于类的一个全局锁,会锁所有调用方法的线程;

3. synchronized @H_403_7@作用于一个对象实例时,锁住的是所有以该对象为锁的代码块。它有多个队列,当多个线程一起访问某个对象监视器的时候,对象监视器会将这些线程存储在不同的容器中。

Synchronized 核心组件

1) Wait Set@H_403_7@:哪些调用 wait @H_403_7@方法被阻塞的线程被放置在这里;

2) Contention List@H_403_7@:竞争队列,所有请求锁的线程首先被放在这个竞争队列中;

3) Entry List@H_403_7@:Contention List @H_403_7@中那些有资格成为候选资源的线程被移动到 Entry List @H_403_7@中;

4) OnDeck@H_403_7@:任意时刻,最多只有一个线程正在竞争锁资源,该线程被成为 OnDeck@H_403_7@;

5) Owner@H_403_7@:当前已经获取到所资源的线程被称为 Owner@H_403_7@;

6) !Owner@H_403_7@:当前释放锁的线程。

 

1. JVM @H_403_7@每次从队列的尾部取出一个数据用于锁竞争候选者(OnDeck@H_403_7@),但是并发情况下,

ContentionList @H_403_7@会被大量的并发线程进行 CAS @H_403_7@访问,为了降低对尾部元素的竞争,JVM @H_403_7@会将一部分线程移动到 EntryList @H_403_7@中作为候选竞争线程。

2. Owner @H_403_7@线程会在 unlock @H_403_7@时,将 ContentionList @H_403_7@中的部分线程迁移到 EntryList @H_403_7@中,并指定EntryList @H_403_7@中的某个线程为 OnDeck @H_403_7@线程(一般是最先进去的那个线程)。

3. Owner @H_403_7@线程并不直接把锁传递给 OnDeck @H_403_7@线程,而是把锁竞争的权利交给 OnDeck@H_403_7@,

OnDeck @H_403_7@需要重新竞争锁。这样虽然牺牲了一些公平性,但是能极大的提升系统的吞吐量,在JVM @H_403_7@中,也把这种选择行为称之为@H_403_7@竞争切换@H_403_7@。

4. OnDeck @H_403_7@线程获取到锁资源后会变为 Owner @H_403_7@线程,而没有得到锁资源的仍然停留在 EntryList@H_403_7@中。如果 Owner @H_403_7@线程被 wait @H_403_7@方法阻塞,则转移到 WaitSet @H_403_7@队列中,直到某个时刻通过 notify@H_403_7@或者 notifyAll @H_403_7@唤醒,会重新进去 EntryList @H_403_7@中。

5. @H_403_7@处于 ContentionList@H_403_7@、EntryList@H_403_7@、WaitSet @H_403_7@中的线程都处于阻塞状态,该阻塞是由操作系统

@H_403_7@来完成的(Linux @H_403_7@内核下采用 pthread_mutex_lock @H_403_7@内核函数实现的)。

6. Synchronized @H_403_7@是非公平锁。 Synchronized @H_403_7@在线程进入 ContentionList @H_403_7@时,等待的线程会先尝试自旋获取锁,如果获取不到就进入 ContentionList@H_403_7@,这明显对于已经进入队列的线程是不公平的,还有一个不公平的事情就是自旋获取锁的线程还可能直接抢占 OnDeck @H_403_7@线程的锁资源。

@H_403_7@参考:https://blog.csdn.net/zqz_zqz/article/details/70233767

7. @H_403_7@每个对象都有个 monitor @H_403_7@对象,加锁就是在竞争 monitor @H_403_7@对象,代码块加锁是在前后分别加上 monitorenter @H_403_7@和 monitorexit @H_403_7@指令来实现的,方法加锁是通过一个标记位来判断的

8. synchronized @H_403_7@是一个重量级操作,需要调用操作系统相关接口,性能是低效的,有可能给线程加锁消耗的时间比有用操作消耗的时间更多。

9. Java1.6@H_403_7@,synchronized @H_403_7@进行了很多的优化,有适应自旋、锁消除、锁粗化、轻量级锁及偏向锁等,效率有了本质上的提高。在之后推出的 Java1.7 @H_403_7@与 1.8 @H_403_7@中,均对该关键字的实现机理做了优化。引入了偏向锁和轻量级锁。都是在对象头中有标记位,不需要经过操作系统加锁。

ReentrantLock

ReentantLock @H_403_7@继承接口 Lock @H_403_7@并实现了接口中定义的方法,他是一种可重入锁,除了能完

@H_403_7@成 synchronized @H_403_7@所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法

Lock 接口的主要方法

1. void lock(): @H_403_7@执行此方法,@H_403_7@如果锁处于空闲状态,@H_403_7@当前线程将获取到锁. @H_403_7@相反,@H_403_7@如果锁已经被其他线程持有,@H_403_7@将禁用当前线程,@H_403_7@直到当前线程获取到锁.

2. boolean tryLock()@H_403_7@:如果锁可用,@H_403_7@则获取,@H_403_7@并立即返回 true,@H_403_7@否则返回 false. @H_403_7@该方法

lock()@H_403_7@的区别在于,tryLock()@H_403_7@只是"@H_403_7@试图"@H_403_7@获取,@H_403_7@如果锁不可用,@H_403_7@不会导致当前线程被禁用,

@H_403_7@当前线程仍然继续往下执行代码. @H_403_7@而 lock()@H_403_7@方法则是一定要获取到锁,@H_403_7@就一

@H_403_7@直等待,@H_403_7@在未获得锁之前,@H_403_7@当前线程并不继续向下执行.

3. void unlock()@H_403_7@:执行此方法,@H_403_7@当前线程将释放持有的锁. @H_403_7@锁只能由持有者释放,@H_403_7@如果线程

@H_403_7@并不持有锁,@H_403_7@却执行该方法,@H_403_7@可能导致异常的发生.

4. Condition newCondition()@H_403_7@:条件对象,获取等待通知组件。该组件和当前的锁绑定,

@H_403_7@当前线程只有获取了锁,才能调用该组件的 await()@H_403_7@方法,而调用后,当前线程将缩放锁。

5. getHoldCount() @H_403_7@:查询当前线程保持此锁的次数,也就是执行此线程执行 lock @H_403_7@方法的次

@H_403_7@数。

6. getQueueLength@H_403_7@():返回正等待获取此锁的线程估计数,比如启动 10 @H_403_7@个线程,1 @H_403_7@个

@H_403_7@线程获得锁,此时返回的是 9

7. getWaitQueueLength@H_403_7@:(Condition condition@H_403_7@)返回等待与此锁相关的给定条件的线

@H_403_7@程估计数。比如 10 @H_403_7@个线程,用同一个 condition @H_403_7@对象,并且此时这 10 @H_403_7@个线程都执行了

condition @H_403_7@对象的 await @H_403_7@方法,那么此时执行此方法返回 10

8. hasWaiters(Condition condition)@H_403_7@:查询是否有线程等待与此锁有关的给定条件

(condition)@H_403_7@,对于指定 contidion @H_403_7@对象,有多少线程执行了 condition.await @H_403_7@方法

9. hasQueuedThread(Thread thread)@H_403_7@:查询给定线程是否等待获取此锁

10. hasQueuedThreads()@H_403_7@:是否有线程等待此锁

11. isFair()@H_403_7@:该锁是否公平锁

12. isHeldByCurrentThread()@H_403_7@: 当前线程是否保持锁锁定,线程的执行 lock @H_403_7@方法的前后分

@H_403_7@别是 false @H_403_7@和 true

13. isLock()@H_403_7@:此锁是否有任意线程占用

14. lockInterruptibly@H_403_7@():如果当前线程未被中断,获取

15. tryLock@H_403_7@():尝试获得锁,仅在调用时锁未被线程占用,获得锁

16. tryLock(long timeout TimeUnit unit)@H_403_7@:如果锁在给定等待时间内没有被另一个线程保持,

@H_403_7@则获取该锁

 

非公平锁

JVM @H_403_7@按随机、就近原则分配锁的机制则称为不公平锁,ReentrantLock @H_403_7@在构造函数中提供了

@H_403_7@是否公平锁的初始化方式,默认为非公平锁。非公平锁实际执行的效率要远远超出公平锁,除非程序有特殊需要,否则最常用非公平锁的分配机制。

公平锁

@H_403_7@公平锁指的是锁的分配机制是公平的,通常先对锁提出获取请求的线程会先被分配到锁,ReentrantLock @H_403_7@在构造函数中提供了是否公平锁的初始化方式来定义公平锁。

ReentrantLock synchronized

1. ReentrantLock @H_403_7@通过方法 lock()@H_403_7@与 unlock()@H_403_7@来进行加锁与解锁操作,与 synchronized @H_403_7@会被 JVM @H_403_7@自动解锁机制不同,ReentrantLock @H_403_7@加锁后需要手动进行解锁。为了避免程序出现异常而无法正常解锁的情况,使用 ReentrantLock @H_403_7@必须在 finally @H_403_7@控制块中进行解锁操作。

2. ReentrantLock @H_403_7@相比 synchronized @H_403_7@的优势是可中断、公平锁、多个锁。这种情况下需要

@H_403_7@使用 ReentrantLock@H_403_7@。

 

 

 

 

 

Condition 类和 Object 类锁方法区别区别

1. Condition @H_403_7@类的 awiat @H_403_7@方法Object @H_403_7@类的 wait @H_403_7@方法等效

2. Condition @H_403_7@类的 signal @H_403_7@方法Object @H_403_7@类的 notify @H_403_7@方法等效

3. Condition @H_403_7@类的 signalAll @H_403_7@方法Object @H_403_7@类的 notifyAll @H_403_7@方法等效

4. ReentrantLock @H_403_7@类可以唤醒指定条件的线程,而 object @H_403_7@的唤醒是随机

@H_403_7@指定条件唤醒,多建立几个condition@H_403_7@。

tryLock lock lockInterruptibly 的区别

1. tryLock @H_403_7@能获得锁就返回 true@H_403_7@,不能就立即返回 false@H_403_7@,tryLock(long timeout,TimeUnit

unit)@H_403_7@,可以增加时间限制,如果超过该时间段还没获得锁,返回 false

2. lock @H_403_7@能获得锁就返回 true@H_403_7@,不能的话一直等待获得锁。

3. lock @H_403_7@和 lockInterruptibly@H_403_7@,如果两个线程分别执行这两个方法,但此时中断这两个线程,

lock @H_403_7@不会抛出异常,而 lockInterruptibly @H_403_7@会抛出异常。

可重入锁的好处

假如一个线程拥有了这个锁。另一个线程需要这个锁,这个时候进行调用。可以直接调用,不用等待重新获取

Semaphore @H_403_7@信号量

Semaphore @H_403_7@是一种基于计数的信号量。它可以设定一个阈值,基于此,多个线程竞争获取许可信号,做完自己的申请后归还,超过阈值后,线程申请许可信号将会被阻塞。Semaphore @H_403_7@可以用来构建一些对象池,资源池之类的,比如数据库连接池。

实现互斥锁(计数器为 1

@H_403_7@我们也可以创建计数为 1 @H_403_7@的 Semaphore@H_403_7@,将其作为一种类似互斥锁的机制,这也叫二元信号量,表示两种互斥状态。

代码实现

 

其他用途

可以创建一个信号量,每个线程消耗一下信号量。用完之后。获取一下剩余数量,如果和初始相等,证明线程内部都执行完毕了,可以继续执行主线程了。

Semaphore ReentrantLock

Semaphore @H_403_7@基本能完成 ReentrantLock @H_403_7@的所有工作,使用方法也与之类似,通过 acquire()@H_403_7@与release()@H_403_7@方法来获得和释放临界资源。经实测,Semaphone.acquire()@H_403_7@方法默认为可响应中断锁,与 ReentrantLock.lockInterruptibly()@H_403_7@作用效果一致,也就是说在等待临界资源的过程中可以被Thread.interrupt()@H_403_7@方法中断。

@H_403_7@此外,Semaphore @H_403_7@也实现了可轮询的锁请求与定时锁的功能,除了方法tryAcquire @H_403_7@与 tryLock@H_403_7@不同,其使用方法ReentrantLock @H_403_7@几乎一致。Semaphore @H_403_7@也提供了公平与非公平锁的机制,也可在构造函数中进行设定。

Semaphore @H_403_7@的锁释放操作也由手动进行,因此与 ReentrantLock @H_403_7@一样,为避免线程因抛出异常而无法正常释放锁的情况发生,释放锁的操作也必须在 finally @H_403_7@代码块中完成。

 

AtomicInteger

@H_403_7@首先说明,此处 AtomicInteger @H_403_7@,一个提供原子操作的 Integer @H_403_7@的类,常见的还有

AtomicBoolean@H_403_7@、AtomicInteger@H_403_7@、AtomicLong@H_403_7@、AtomicReference @H_403_7@等,他们的实现原理相同,

@H_403_7@区别在与运算对象类型的不同。令人兴奋地,还可以通过 AtomicReference<V>@H_403_7@将一个对象的所有操作转化成原子操作。

@H_403_7@我们知道,在多线程程序中,诸如++i @H_403_7@或 i++@H_403_7@等运算不具有原子性,是不安全的线程操作之一。

@H_403_7@通常我们会使用 synchronized @H_403_7@将该操作变成一个原子操作,但 JVM @H_403_7@为此类操作特意提供了一些同步类,使得使用更方便,且使程序运行效率变得更高。通过相关资料显示,通常AtomicInteger@H_403_7@的性能ReentantLock @H_403_7@的好几倍。

 

@H_403_7@可重入锁(递归锁)

@H_403_7@本文里面讲的是广义上的可重入锁,而不是单指 JAVA @H_403_7@下的 ReentrantLock@H_403_7@。可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。在 JAVA @H_403_7@环境下 ReentrantLock @H_403_7@和 synchronized @H_403_7@都是可重入锁。

@H_403_7@公平锁与非公平锁

@H_403_7@公平锁(Fair@H_403_7@)

@H_403_7@加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得

@H_403_7@非公平锁(Nonfair@H_403_7@)

@H_403_7@加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待

1. @H_403_7@非公平锁性能比公平锁高 5~10 @H_403_7@倍,因为公平锁需要在多核的情况下维护一个队列

2. Java @H_403_7@中的 synchronized @H_403_7@是非公平锁,ReentrantLock @H_403_7@默认的 lock()@H_403_7@方法采用的是非公平锁。

ReadWriteLock @H_403_7@读写锁

@H_403_7@为了提高性能,Java @H_403_7@提供了读写锁,在读的地方使用读锁,在写的地方使用写锁,灵活控制,如果没有写锁的情况下,读是无阻塞的,@H_403_7@在一定程度上提高了程序的执行效率。读写锁分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,这是由 jvm @H_403_7@自己控制的,你只要上好相应的锁即可。

@H_403_7@读锁

@H_403_7@如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁。

@H_403_7@写锁

@H_403_7@如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上读锁,写的时候上写锁!

Java @H_403_7@中读写锁有个接口 java.util.concurrent.locks.ReadWriteLock @H_403_7@,也有具体的实现

ReentrantReadWriteLock@H_403_7@。

@H_403_7@共享锁和独占锁

独占锁

@H_403_7@独占锁模式下,每次只能有一个线程能持有锁,ReentrantLock @H_403_7@就是以独占方式实现的互斥锁。

@H_403_7@独占锁是一种悲观保守的加锁策略,它避免了读/@H_403_7@读冲突,如果某个只读线程获取锁,则其他读线程都只能等待,这种情况下就限制了不必要的并发性,因为读操作并不会影响数据的一致性。

共享锁

@H_403_7@共享锁则允许多个线程同时获取锁,并发访问 @H_403_7@共享资源,如:ReadWriteLock@H_403_7@。共享锁则是一种乐观锁,它放宽了加锁策略,允许多个执行读操作的线程同时访问共享资源。

1. AQS @H_403_7@的内部类 Node @H_403_7@定义了两个常量 SHARED @H_403_7@和 EXCLUSIVE@H_403_7@,他们分别标识 AQS @H_403_7@队列中等待线程的锁获取模式。

2. java @H_403_7@的并发包中提供了 ReadWriteLock@H_403_7@,读-@H_403_7@写锁。它允许一个资源可以被多个读操作访问,

@H_403_7@或者被一个写操作访问,但两者不能同时进行。

@H_403_7@重量级锁(Mutex Lock@H_403_7@)

Synchronized @H_403_7@是通过对象内部的一个叫做监视器锁(monitor@H_403_7@)来实现的。但是监视器锁本质又是依赖于底层的操作系统的 Mutex Lock @H_403_7@来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么Synchronized @H_403_7@效率低的原因。因此,这种依赖于操作系统 Mutex Lock @H_403_7@所实现的锁我们称之为@H_403_7@重量级锁@H_403_7@。JDK @H_403_7@中对 Synchronized @H_403_7@做的种种优化,其核心都是为了减少这种重量级锁的使用。

JDK1.6 @H_403_7@以后,为了减少获得锁和释放锁所带来的性能消耗,提高性能,引入了@H_403_7@轻量级锁@H_403_7@和@H_403_7@偏向锁@H_403_7@。

@H_403_7@轻量级锁

@H_403_7@锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。

升级

@H_403_7@随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)。

“@H_403_7@轻量级@H_403_7@是相对于使用操作系统互斥量来实现的传统锁而言的。但是,首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。在解释轻量级锁的执行过程之前,先明白一点,轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。

@H_403_7@偏向锁

Hotspot @H_403_7@的作者经过以往的研究发现大多数情况下锁不仅不存在多线程竞争,而且总是由同一线程多次获得。偏向锁的目的是在某个线程获得锁之后,消除这个线程锁重入(CAS@H_403_7@)的开销,看起来让这个线程得到了偏护。引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次 CAS @H_403_7@原子指令,而偏向锁只需要在置换ThreadID @H_403_7@的时候依赖一次 CAS @H_403_7@原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的 CAS @H_403_7@原子指令的性能消耗)。上面说过,轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能

@H_403_7@分段锁

@H_403_7@分段锁也并非一种实际的锁,而是一种思想 ConcurrentHashMap @H_403_7@是学习分段锁的最好实践。

@H_403_7@锁优化

减少锁持有时间

@H_403_7@只用在有线程安全要求的程序上加锁

减小锁粒度

@H_403_7@将大对象(这个对象可能会被很多线程访问),拆成小对象,大大增加并行度,降低锁竞争。

@H_403_7@降低了锁的竞争,偏向锁,轻量级锁成功率才会提高。最最典型的减小锁粒度的案例就是

ConcurrentHashMap@H_403_7@。

锁分离

@H_403_7@最常见的锁分离就是读写锁 ReadWriteLock@H_403_7@,根据功能进行分离成读锁和写锁,这样读读不互斥,读写互斥,写写互斥,即保证了线程安全,又提高了性能。JDK @H_403_7@并发包 1@H_403_7@。读写分离思想可以延伸,只要操作互不影响,锁就可以分离。比如LinkedBlockingQueue @H_403_7@从头部取出,从尾部放数据。

锁粗化

@H_403_7@通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短,即在使用完公共资源后,应该立即释放锁。但是,凡事都有一个度,如果对同一个锁不停的进行请求、同步和释放,其本身也会消耗系统宝贵的资源,反而不利于性能的优化 @H_403_7@。

锁消除

@H_403_7@锁消除是在编译器级别的事情。在即时编译器时,如果发现不可能被共享的对象,则可以消除这些对象的锁操作,多数是因为程序员编码不规范引起。

9. 线程基本方法

 

 

 

@H_403_7@线程等待(wait@H_403_7@)

@H_403_7@调用方法的线程进入 WAITING @H_403_7@状态,只有等待另外线程的通知或被中断才会返回,需要注意的是调用 wait()@H_403_7@方法后,会释放对象的锁。因此,wait @H_403_7@方法一般用在同步方法或同步代码块中。

@H_403_7@线程睡眠(sleep@H_403_7@)

sleep @H_403_7@导致当前线程休眠,与 wait @H_403_7@方法不同的是 sleep @H_403_7@不会释放当前占有的锁,sleep(long)@H_403_7@会导致线程进入 TIMED-WATING @H_403_7@状态,而 wait()@H_403_7@方法会导致当前线程进入 WATING @H_403_7@状态。

@H_403_7@线程让步(yield@H_403_7@)

yield @H_403_7@会使当前线程让出 cpu @H_403_7@执行时间片,与其他线程一起重新竞争 cpu @H_403_7@时间片。一般情况下,优先级高的线程有更大的可能性成功竞争得到 cpu @H_403_7@时间片,但这又不是绝对的,有的操作系统对线程优先级并不敏感。

@H_403_7@线程中断(interrupt@H_403_7@)

@H_403_7@中断一个线程,其本意是给这个线程一个通知信号,会影响这个线程内部的一个中断标识位。这个线程本身并不会因此而改变状态(@H_403_7@如阻塞,终止等)@H_403_7@。

1. @H_403_7@调用 interrupt()@H_403_7@方法并不会中断一个正在运行的线程。也就是说处于 Running @H_403_7@状态的线

@H_403_7@程并不会因为被中断而被终止,仅仅改变了内部维护的中断标识位而已。

2. @H_403_7@若调用 sleep()@H_403_7@而使线程处于 TIMED-WATING @H_403_7@状态,这时调用 interrupt()@H_403_7@方法,会抛出

InterruptedException,@H_403_7@从而使线程提前结束 TIMED-WATING @H_403_7@状态。

3. @H_403_7@许多声明抛出 InterruptedException @H_403_7@的方法(@H_403_7@如 Thread.sleep(long mills @H_403_7@方法))@H_403_7@,抛出异

@H_403_7@常前,都会清除中断标识位,所以抛出异常后,调用 isInterrupted()@H_403_7@方法将会返回 false@H_403_7@。

4. @H_403_7@中断状态是线程固有的一个标识位,可以通过此标识位安全的终止线程。比如,@H_403_7@你想终止

@H_403_7@一个线程 thread @H_403_7@的时候,可以调用 thread.interrupt()@H_403_7@方法,在线程的 run @H_403_7@方法内部可以

@H_403_7@根据 thread.isInterrupted()@H_403_7@的值来优雅的终止线程。

Join @H_403_7@等待其他线程终止

join() @H_403_7@方法,等待其他线程终止,在当前线程中调用一个线程的 join() @H_403_7@方法,则当前线程转为阻塞状态,回到另一个线程结束,当前线程再由阻塞状态变为就绪状态,等待 cpu @H_403_7@的宠幸。

@H_403_7@为什么要用 join()@H_403_7@方法

@H_403_7@很多情况下,主线程生成并启动了子线程,需要用到子线程返回的结果,也就是需要主线程需要在子线程结束后再结束,这时候就要用到 join() @H_403_7@方法

 

 

 

@H_403_7@线程唤醒(notify@H_403_7@)

Object @H_403_7@类中的 notify() @H_403_7@方法,唤醒在此对象监视器上等待的单个线程,如果所有线程都在此对象上等待,则会选择唤醒其中一个线程,选择是任意的,并在对实现做出决定时发生,线程通过调用其中一个 wait() @H_403_7@方法,在对象的监视器上等待,直到当前的线程放弃此对象上的锁定,才能继续执行被唤醒的线程,被唤醒的线程将以常规方式与在该对象上主动同步的其他所有线程进行竞争。类似的方法还有 notifyAll() @H_403_7@,唤醒再次监视器上等待的所有线程。

@H_403_7@其他方法

1. sleep()@H_403_7@:强迫一个线程睡眠N毫秒。

2. isAlive()@H_403_7@: 判断一个线程是否存活。

3. join()@H_403_7@: 等待线程终止。

4. activeCount()@H_403_7@: 程序中活跃的线程数。

5. enumerate()@H_403_7@: 枚举程序中的线程。

6. currentThread()@H_403_7@: 得到当前线程。

7. isDaemon()@H_403_7@: 一个线程是否为守护线程。

8. setDaemon()@H_403_7@: 设置一个线程为守护线程。(@H_403_7@用户线程和守护线程的区别在于,是否等待主线

@H_403_7@程依赖于主线程结束而结束)

9. setName()@H_403_7@: 为线程设置一个名称

10. wait()@H_403_7@: 强迫一个线程等待。

11. notify()@H_403_7@: 通知一个线程继续运行。

12. setPriority()@H_403_7@: 设置一个线程的优先级。

13. getPriority():@H_403_7@:获得一个线程的优先级。

10. 线程的上下文切换

@H_403_7@巧妙地利用了时间片轮转的方式,cpu @H_403_7@给每个任务都服务一定的时间,然后把当前任务的状态保存下来,在加载下一任务的状态后,继续服务下一任务,任务的状态保存及再加载,@H_403_7@这段过程就叫做上下文切换。时间片轮转的方式使多个任务在同一颗 cpu @H_403_7@上执行变成了可能。

 

 

 

线@H_403_7@程

@H_403_7@(有时候也称做任务)是指一个程序运行的实例。在 Linux @H_403_7@系统中,线程就是能并行运行并且与他们的父进程(创建他们的进程)共享同一地址空间(一段内存区域)和其他资源的轻量级的进程。

@H_403_7@上下文

@H_403_7@是指某一时间点 cpu @H_403_7@寄存器和程序计数器的内容

@H_403_7@寄存器

@H_403_7@是 cpu @H_403_7@内部的数量较少但是速度很快的内存(与之对应的是 cpu @H_403_7@外部相对较慢的 RAM @H_403_7@主内存)。寄存器通过对常用值(通常是运算的中间值)的快速访问来提高计算机程序运行的速度。

@H_403_7@程序计数器

@H_403_7@是一个专用的寄存器,用于表明指令序列中 cpu @H_403_7@正在执行的位置,存的值为正在执行的指令的位置或者下一个将要被执行的指令的位置,具体依赖于特定的系统。

PCB-“@H_403_7@切换桢

@H_403_7@上下文切换可以认为是内核(操作系统的核心)在 cpu @H_403_7@上对于进程(包括线程)进行切换,上下文切换过程中的信息是保存在进程控制块(PCB,process control block@H_403_7@)中的。PCB @H_403_7@还经常被称作@H_403_7@切换桢@H_403_7@(switchframe@H_403_7@)。信息会一直保存到 cpu @H_403_7@的内存中,直到他们被再次使用。

@H_403_7@上下文切换的活动

1. @H_403_7@挂起一个进程,将这个进程在 cpu @H_403_7@中的状态(上下文)存储于内存中的某处。

2. @H_403_7@在内存中检索下一个进程的上下文并将其在 cpu @H_403_7@的寄存器中恢复。

3. @H_403_7@跳转到程序计数器所指向的位置(即跳转到进程被中断时的代码行),以恢复该进程在程序中。

@H_403_7@引起线程上下文切换的原因

1. @H_403_7@当前执行任务的时间片用完之后,系统 cpu @H_403_7@正常调度下一个任务;

2. @H_403_7@当前执行任务碰到 IO @H_403_7@阻塞,调度器将此任务挂起,继续下一任务;

3. @H_403_7@多个任务抢占锁资源,当前任务没有抢到锁资源,被调度器挂起,继续下一任务;

4. @H_403_7@用户代码挂起当前任务,让出 cpu @H_403_7@时间;

5. @H_403_7@硬件中断;

11. 同步锁与死锁

@H_403_7@同步锁

@H_403_7@当多个线程同时访问同一个数据时,很容易出现问题。为了避免这种情况出现,我们要保证线程同步互斥,就是指并发执行的多个线程,在同一时间内只允许一个线程访问共享数据。 Java @H_403_7@中可以使用 synchronized @H_403_7@关键字来取得一个对象的同步锁。

 

@H_403_7@死锁

@H_403_7@何为死锁,就是多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。

12. 线程池原理

@H_403_7@线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。他的主要特点为:线程复用;控制最大并发数;管理线程。

@H_403_7@线程复用

@H_403_7@每一个 Thread @H_403_7@的类都有一个 start @H_403_7@方法。 当调用 start @H_403_7@启动线程时 Java @H_403_7@虚拟机会调用该类的 run @H_403_7@方法。 那么该类的 run() @H_403_7@方法中就是调用Runnable @H_403_7@对象的 run() @H_403_7@方法。 我们可以继承重写Thread @H_403_7@类,在其 start @H_403_7@方法添加不断循环调用传递过来的 Runnable @H_403_7@对象。 这就是线程池的实现原理。循环方法中不断获取 Runnable @H_403_7@是用 Queue @H_403_7@实现的,在获取下一个 Runnable @H_403_7@之前可以是阻塞的。

@H_403_7@线程池的组成

@H_403_7@一般的线程池主要分为以下 4 @H_403_7@个组成部分:

1. @H_403_7@线程池管理器:用于创建并管理线程池

2. @H_403_7@工作线程:线程池中的线程

3. @H_403_7@任务接口:每个任务必须实现的接口,用于工作线程调度其运行

4. @H_403_7@任务队列:用于存放待处理的任务,提供一种缓冲机制

Java @H_403_7@中的线程池是通过 Executor @H_403_7@框架实现的,该框架中用到了 Executor@H_403_7@,Executors@H_403_7@,

ExecutorService@H_403_7@,ThreadPoolExecutor @H_403_7@,Callable @H_403_7@和 Future@H_403_7@、FutureTask @H_403_7@这几个类。

 

ThreadPoolExecutor @H_403_7@的构造方法如下:

1. corePoolSize@H_403_7@:指定了线程池中的线程数量

2. maximumPoolSize@H_403_7@:指定了线程池中的最大线程数量

3. keepAliveTime@H_403_7@:当前线程池数量超过 corePoolSize @H_403_7@时,多余的空闲线程的存活时间,即多

@H_403_7@次时间内会被销毁。

4. unit@H_403_7@:keepAliveTime @H_403_7@的单位。

5. workQueue@H_403_7@:任务队列,被提交但尚未被执行的任务。

6. threadFactory@H_403_7@:线程工厂,用于创建线程,一般用默认的即可。

7. handler@H_403_7@:拒绝策略,当任务太多来不及处理,如何拒绝任务。

@H_403_7@拒绝策略

@H_403_7@线程池中的线程已经用完了,无法继续为新任务服务,同时,等待队列也已经排满了,再也

@H_403_7@塞不下新任务了。这时候我们就需要拒绝策略机制合理的处理这个问题。

JDK @H_403_7@内置的拒绝策略如下:

1. AbortPolicy @H_403_7@: 直接抛出异常,阻止系统正常运行。

2. CallerRunsPolicy @H_403_7@: 只要线程池未关闭,该策略直接在调用者线程中,运行当前被丢弃的

@H_403_7@任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能会急剧下降。

3. DiscardOldestPolicy @H_403_7@: 丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再

@H_403_7@次提交当前任务。

4. DiscardPolicy @H_403_7@: 该策略默默地丢弃无法处理的任务,不予任何处理。如果允许任务丢

@H_403_7@失,这是最好的一种方案。

@H_403_7@以上内置拒绝策略均实现了 RejectedExecutionHandler @H_403_7@接口,若以上策略仍无法满足实际

@H_403_7@需要,完全可以自己扩展 RejectedExecutionHandler @H_403_7@接口。

Java @H_403_7@线程池工作过程

1. @H_403_7@线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行它们。

2. @H_403_7@当调用 execute() @H_403_7@方法添加一个任务时,线程池会做如下判断:

a) @H_403_7@如果正在运行的线程数量小于 corePoolSize@H_403_7@,那么马上创建线程运行这个任务;

b) @H_403_7@如果正在运行的线程数量大于或等于 corePoolSize@H_403_7@,那么将这个任务放入队列;

c) @H_403_7@如果这时候队列满了,而且正在运行的线程数量小于 maximumPoolSize@H_403_7@,那么还是要

@H_403_7@创建非核心线程立刻运行这个任务;

d) @H_403_7@如果队列满了,而且正在运行的线程数量大于或等于 maximumPoolSize@H_403_7@,那么线程池

@H_403_7@会抛出异常 RejectExecutionException@H_403_7@。

3. @H_403_7@当一个线程完成任务时,它会从队列中取下一个任务来执行。

4. @H_403_7@当一个线程无事可做,超过一定的时间(keepAliveTime@H_403_7@)时,线程池会判断,如果当前运行的线程数大于 corePoolSize@H_403_7@,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到 corePoolSize @H_403_7@的大小。

 

13. JAVA 阻塞队列原理

@H_403_7@阻塞队列,关键字是阻塞,先理解阻塞的含义,在阻塞队列中,线程阻塞有这样的两种情况:

  1. @H_403_7@当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列。

 

  1. @H_403_7@当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒。

 

@H_403_7@阻塞队列的主要方法

 

 

 

„ @H_403_7@抛出异常:抛出一个异常;

„ @H_403_7@特殊值:返回一个特殊值(null @H_403_7@或 false,@H_403_7@视情况而定)

„ @H_403_7@则塞:在成功操作之前,一直阻塞线程

„ @H_403_7@超时:放弃前只在最大的时间内阻塞

插入操作:

1@H_403_7@:public abstract boolean add(E paramE)@H_403_7@:将指定元素插入此队列中(如果立即可行

@H_403_7@且不会违反容量限制),成功时返回 true@H_403_7@,如果当前没有可用的空间,则抛

@H_403_7@出 IllegalStateException@H_403_7@。如果该元素是 NULL@H_403_7@,则会抛出 NullPointerException @H_403_7@异常。

2@H_403_7@:public abstract boolean offer(E paramE)@H_403_7@:将指定元素插入此队列中(如果立即可行

@H_403_7@且不会违反容量限制),成功时返回 true@H_403_7@,如果当前没有可用的空间,则返回 false@H_403_7@。

3@H_403_7@:public abstract void put(E paramE) throws InterruptedException@H_403_7@: 将指定元素插入此队列中,将等待可用的空间(如果有必要)

public void put(E paramE) throws InterruptedException {

 checkNotNull(paramE);

 ReentrantLock localReentrantLock = this.lock;

 localReentrantLock.lockInterruptibly();

 try {

 while (this.count == this.items.length)

 this.notFull.await();//@H_403_7@如果队列满了,则线程阻塞等待

 enqueue(paramE);

 localReentrantLock.unlock();

 } finally {

 localReentrantLock.unlock();

 }

 }

 4@H_403_7@:offer(E o,long timeout,TimeUnit unit)@H_403_7@:可以设定等待的时间,如果在指定的时间

@H_403_7@内,还不能往队列中加入 BlockingQueue@H_403_7@,则返回失败。

获取数据操作:

1@H_403_7@:poll(time):@H_403_7@取走 BlockingQueue @H_403_7@里排在首位的对象,@H_403_7@若不能立即取出,@H_403_7@则可以等 time @H_403_7@参数

@H_403_7@规定的时间,@H_403_7@取不到时返回 null;

2@H_403_7@:poll(long timeout,TimeUnit unit)@H_403_7@:从 BlockingQueue @H_403_7@取出一个队首的对象,如果在

@H_403_7@指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则直到时间超时还没有数

@H_403_7@据可取,返回失败。

3@H_403_7@:take():@H_403_7@取走 BlockingQueue @H_403_7@里排在首位的对象,@H_403_7@若 BlockingQueue @H_403_7@为空,@H_403_7@阻断进入等待状

@H_403_7@态直到 BlockingQueue @H_403_7@有新的数据被加入。

4.drainTo():@H_403_7@一次性从 BlockingQueue @H_403_7@获取所有可用的数据对象(还可以指定获取数据的个

@H_403_7@数),通过该方法,可以提升获取数据效率;不需要多次分批加锁或释放锁。

Java @H_403_7@中的阻塞队列

1. ArrayBlockingQueue @H_403_7@:由数组结构组成的有界阻塞队列。

2. LinkedBlockingQueue @H_403_7@:由链表结构组成的有界阻塞队列。

3. PriorityBlockingQueue @H_403_7@:支持优先级排序的无界阻塞队列。

4. DelayQueue@H_403_7@:使用优先级队列实现的无界阻塞队列。

5. SynchronousQueue@H_403_7@:不存储元素的阻塞队列。

6. LinkedTransferQueue@H_403_7@:由链表结构组成的无界阻塞队列。

7. LinkedBlockingDeque@H_403_7@:由链表结构组成的双向阻塞队列

ArrayBlockingQueue(公平、非公平)

@H_403_7@用数组实现的有界阻塞队列。此队列按照先进先出(FIFO@H_403_7@)的原则对元素进行排序。默认情况下不保证访问者公平的访问队列,所谓公平访问队列是指阻塞的所有生产者线程或消费者线程,当队列可用时,可以按照阻塞的先后顺序访问队列,即先阻塞的生产者线程,可以先往队列里插入元素,先阻塞的消费者线程,可以先从队列里获取元素。通常情况下为了保证公平性会降低吞吐量。我们可以使用以下代码创建一个公平的阻塞队列:

ArrayBlockingQueue fairQueue = new ArrayBlockingQueue(1000,true);

LinkedBlockingQueue(两个独立锁提高并发)

@H_403_7@基于链表的阻塞队列,同 ArrayListBlockingQueue @H_403_7@类似,此队列按照先进先出(FIFO@H_403_7@)的原则对元素进行排序。而 LinkedBlockingQueue @H_403_7@之所以能够高效的处理并发数据,还因为其对于生产者端和消费者端分别采用了独立的锁来控制数据同步,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能

LinkedBlockingQueue @H_403_7@会默认一个类似无限大小的容量(Integer.MAX_VALUE@H_403_7@)。

PriorityBlockingQueuecompareTo 排序实现优先)

@H_403_7@是一个支持优先级的无界队列。默认情况下元素采取自然顺序升序排列。可以自定义实现

compareTo()@H_403_7@方法来指定元素进行排序规则,或者初始化 PriorityBlockingQueue @H_403_7@时,指定构造参数 Comparator @H_403_7@来对元素进行排序。需要注意的是不能保证同优先级元素的顺序。

DelayQueue(缓存失效、定时任务 )

@H_403_7@是一个支持延时获取元素的无界阻塞队列。队列使用 PriorityQueue @H_403_7@来实现。队列中的元素必须实现 Delayed @H_403_7@接口,在创建元素时可以指定多久才能从队列中获取当前元素。只有在延迟期满时才能从队列中提取元素。我们可以将 DelayQueue @H_403_7@运用在以下应用场景:

1. @H_403_7@缓存系统的设计:可以用 DelayQueue @H_403_7@保存缓存元素的有效期,使用一个线程循环查询

DelayQueue@H_403_7@,一旦能从 DelayQueue @H_403_7@中获取元素时,表示缓存有效期到了。

2. @H_403_7@定时任务调度:使用 DelayQueue @H_403_7@保存当天将会执行的任务和执行时间,一旦从

DelayQueue @H_403_7@中获取到任务就开始执行,从比如 TimerQueue @H_403_7@就是使用 DelayQueue @H_403_7@实现的。

SynchronousQueue(不存储数据、可用于传递数据)

@H_403_7@是一个不存储元素的阻塞队列。每一个 put @H_403_7@操作必须等待一个 take @H_403_7@操作,否则不能继续添加元素。

SynchronousQueue @H_403_7@可以看成是一个传球手,负责把生产者线程处理的数据直接传递给消费者线程。队列本身并不存储任何元素,非常适合于传递性场景,@H_403_7@比如在一个线程中使用的数据,传递给另外一个线程使用, SynchronousQueue @H_403_7@的吞吐量高于 LinkedBlockingQueue @H_403_7@和

ArrayBlockingQueue@H_403_7@。

LinkedTransferQueue

@H_403_7@是一个由链表结构组成的无界阻塞 TransferQueue @H_403_7@队列。相对于其他阻塞队列,

LinkedTransferQueue @H_403_7@多了 tryTransfer @H_403_7@和 transfer @H_403_7@方法

1. transfer @H_403_7@方法:如果当前有消费者正在等待接收元素(消费者使用 take()@H_403_7@方法或带时间限制的poll()@H_403_7@方法时),transfer @H_403_7@方法可以把生产者传入的元素立刻 transfer@H_403_7@(传输)给消费者。如果没有消费者在等待接收元素,transfer @H_403_7@方法会将元素存放在队列的 tail @H_403_7@节点,并等到该元素被消费者消费了才返回。

2. tryTransfer @H_403_7@方法。则是用来试探下生产者传入的元素是否能直接传给消费者。如果没有消费者等待接收元素,则返回 false@H_403_7@。和 transfer @H_403_7@方法的区别是 tryTransfer @H_403_7@方法无论消费者是否接收,方法立即返回。而 transfer @H_403_7@方法是必须等到消费者消费了才返回。

@H_403_7@对于带有时间限制的 tryTransfer(E e,TimeUnit unit)@H_403_7@方法,则是试图把生产者传

@H_403_7@入的元素直接传给消费者,但是如果没有消费者消费该元素则等待指定的时间再返回,如果超时还没消费元素,则返回 false@H_403_7@,如果在超时时间内消费了元素,则返回 true@H_403_7@。

LinkedBlockingDeque

@H_403_7@是一个由链表结构组成的双向阻塞队列。所谓双向队列指的你可以从队列的两端插入和移出元素。双端队列因为多了一个操作队列的入口,在多线程同时入队时,也就减少了一半的竞争。相比其他的阻塞队列,LinkedBlockingDeque @H_403_7@多了 addFirst@H_403_7@,addLast@H_403_7@,offerFirst@H_403_7@,offerLast@H_403_7@,

peekFirst@H_403_7@,peekLast @H_403_7@等方法,以 First @H_403_7@单词结尾的方法,表示插入,获取peek@H_403_7@)或移除双端队列的第一个元素。以 Last @H_403_7@单词结尾的方法,表示插入,获取或移除双端队列的最后一个元素。另外插入方法 add @H_403_7@等同于 addLast@H_403_7@,移除方法 remove @H_403_7@等效于 removeFirst@H_403_7@。但是 take @H_403_7@方法却等同于 takeFirst@H_403_7@,不知道是不是 Jdk @H_403_7@的 bug@H_403_7@,使用时还是用带有 First @H_403_7@和 Last @H_403_7@后缀的方法更清楚。

@H_403_7@在初始化 LinkedBlockingDeque @H_403_7@时可以设置容量防止其过渡膨胀。另外双向阻塞队列可以运用在@H_403_7@工作窃取@H_403_7@模式中。

14. CyclicBarrierCountDownLatchSemaphore 用法

CountDownLatch@H_403_7@(线程计数器)

CountDownLatch @H_403_7@类位于 java.util.concurrent @H_403_7@包下,利用它可以实现类似计数器的功能。比如有一个任务 A@H_403_7@,它要等待其他 4 @H_403_7@个任务执行完毕之后才能执行,此时就可以利用 CountDownLatch@H_403_7@来实现这种功能了。

 

 

 

 

CyclicBarrier@H_403_7@(回环栅栏-@H_403_7@等待至 barrier @H_403_7@状态再全部同时执行)

@H_403_7@字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier @H_403_7@可以被重用。我们暂且把这个状态就叫做barrier@H_403_7@,当调用 await()@H_403_7@方法之后,线程就处于 barrier @H_403_7@了。

CyclicBarrier @H_403_7@中最重要的方法就是 await @H_403_7@方法,它有 2 @H_403_7@个重载版本:

1. public int await()@H_403_7@:用来挂起当前线程,直至所有线程都到达 barrier @H_403_7@状态再同时执行后续任务;

2. public int await(long timeout,TimeUnit unit)@H_403_7@:让这些线程等待至一定的时间,如果还有

@H_403_7@线程没有到达 barrier @H_403_7@状态就直接让到达 barrier @H_403_7@的线程执行后续任务。

@H_403_7@具体使用如下,另外 CyclicBarrier @H_403_7@是可以重用的。

 

 

 

Semaphore@H_403_7@(信号量-@H_403_7@控制同时访问的线程个数)

Semaphore @H_403_7@翻译成字面意思为信号量,Semaphore @H_403_7@可以控制同时访问的线程个数,通过

acquire() @H_403_7@获取一个许可,如果没有就等待,而 release() @H_403_7@释放一个许可。

Semaphore @H_403_7@类中比较重要的几个方法

1. public void acquire(): @H_403_7@用来获取一个许可,若无许可能够获得,则会一直等待,直到获得许

@H_403_7@可。

2. public void acquire(int permits):@H_403_7@获取 permits @H_403_7@个许可

3. public void release() { } :@H_403_7@释放许可。注意,在释放许可之前,必须先获获得许可。

4. public void release(int permits) { }:@H_403_7@释放 permits @H_403_7@个许可

@H_403_7@上面 4 @H_403_7@个方法都会被阻塞,如果想立即得到执行结果,可以使用下面几个方法

1. public boolean tryAcquire():@H_403_7@尝试获取一个许可,若获取成功,则立即返回 true@H_403_7@,若获取

@H_403_7@败,则立即返回 false

2. public boolean tryAcquire(long timeout,TimeUnit unit):@H_403_7@尝试获取一个许可,若在指定的

@H_403_7@时间内获取成功,则立即返回 true@H_403_7@,否则则立即返回 false

3. public boolean tryAcquire(int permits):@H_403_7@尝试获取 permits @H_403_7@个许可,若获取成功,则立即返

@H_403_7@回 true@H_403_7@,若获取失败,则立即返回 false

4. public boolean tryAcquire(int permits,TimeUnit unit): @H_403_7@尝试获取 permits

@H_403_7@个许可,若在指定的时间内获取成功,则立即返回 true@H_403_7@,否则则立即返回 false

5. @H_403_7@还可以通过 availablePermits()@H_403_7@方法得到可用的许可数目。

@H_403_7@例子:若一个工厂有 5 @H_403_7@台机器,但是有 8 @H_403_7@个工人,一台机器同时只能被一个工人使用,只有使用完

@H_403_7@了,其他工人才能继续使用。那么我们就可以通过 Semaphore @H_403_7@来实现:

 

 

 

CountDownLatch @H_403_7@和 CyclicBarrier @H_403_7@都能够实现线程之间的等待,只不过它们侧重点不

@H_403_7@同;CountDownLatch @H_403_7@一般用于某个线程 A @H_403_7@等待若干个其他线程执行完任务之后,它才

@H_403_7@执行;而 CyclicBarrier @H_403_7@一般用于一组线程互相等待至某个状态,然后这一组线程再同时

@H_403_7@执行;另外,CountDownLatch @H_403_7@是不能够重用的,而 CyclicBarrier @H_403_7@是可以重用的。

Semaphore @H_403_7@其实和锁有点类似,它一般用于控制对某组资源的访问权限。

15. volatile 关键字的作用(变量可见性、禁止重排序)

Java @H_403_7@语言提供了一种稍弱的同步机制,即 volatile @H_403_7@变量,用来确保将变量的更新操作通知到其他线程。volatile @H_403_7@变量具备两种特性,volatile @H_403_7@变量不会被缓存在寄存器或者对其他处理器不可见的地方,因此在读取 volatile @H_403_7@类型的变量时总会返回最新写入的值。

@H_403_7@变量可见性

@H_403_7@其一是保证该变量对所有线程可见,这里的可见性指的是当一个线程修改了变量的值,那么新的值对于其他线程是可以立即获取的。

@H_403_7@禁止重排序

volatile @H_403_7@禁止了指令重排。 比 sychronized @H_403_7@更轻量级的同步锁

@H_403_7@在访问 volatile @H_403_7@变量时不会执行加锁操作,因此也就不会使执行线程阻塞,因此 volatile @H_403_7@变量是一种比 sychronized @H_403_7@关键字更轻量级的同步机制。volatile @H_403_7@适合这种场景:一个变量被多个线程共享,线程直接给这个变量赋值。

@H_403_7@当对非 volatile @H_403_7@变量进行读写的时候,每个线程先从内存拷贝变量到 cpu @H_403_7@缓存中。如果计算机有多个 cpu@H_403_7@,每个线程可能在不同的 cpu @H_403_7@上被处理,这意味着每个线程可以拷贝到不同的 cpu  cache @H_403_7@中。而声明变量是 volatile @H_403_7@的,JVM @H_403_7@保证了每次读变量都从内存中读,跳过 cpu cache @H_403_7@这一步。

@H_403_7@适用场景

@H_403_7@值得说明的是对 volatile @H_403_7@变量的单次读/@H_403_7@写操作可以保证原子性的,如 long @H_403_7@和 double @H_403_7@类型变量,但是并不能保证 i++@H_403_7@这种操作的原子性,因为本质上 i++@H_403_7@是读、写两次操作。在某些场景下可以代替 Synchronized@H_403_7@。但是,volatile @H_403_7@的不能完全取代 Synchronized @H_403_7@的位置,只有在一些特殊的场景下,才能适用 volatile@H_403_7@。总的来说,必须同时满足下面两个条件才能保证在并发环境的线程安全:

@H_403_7@(1@H_403_7@)对变量的写操作不依赖于当前值(比如 i++@H_403_7@),或者说是单纯的变量赋值(boolean

flag = true@H_403_7@)。

(2)@H_403_7@该变量没有包含在具有其他变量的不变式中,也就是说,不同的 volatile @H_403_7@变量之间,不能互相依赖。只有在状态真正独立于程序内其他内容时才能使用 volatile

16. 如何在两个线程之间共享数据

Java @H_403_7@里面进行多线程通信的主要方式就是共享内存的方式,共享内存主要的关注点有两个:可见性和有序性原子性。Java @H_403_7@内存模型(JMM@H_403_7@)解决了可见性和有序性的问题,而锁解决了原子性的问题,理想情况下我们希望做到@H_403_7@同步@H_403_7@和@H_403_7@互斥@H_403_7@。有以下常规实现方法

@H_403_7@将数据抽象成一个类,并将数据的操作作为这个类的方法

  1. @H_403_7@将数据抽象成一个类,并将对这个数据的操作作为这个类的方法,这么设计可以和容易做到同步,只要在方法上加”synchronized“。

Runnable @H_403_7@对象作为一个类的内部类

  1. @H_403_7@将 Runnable @H_403_7@对象作为一个类的内部类,共享数据作为这个类的成员变量,每个线程对共享数据的操作方法也封装在外部类,以便实现对数据的各个操作的同步和互斥,作为内部类的各个 Runnable @H_403_7@对象调用外部类的这些方法

17. ThreadLocal 作用(线程本地存储)

ThreadLocal@H_403_7@,很多地方叫做线程本地变量,也有些地方叫做线程本地存储,ThreadLocal @H_403_7@的作用是提供线程内的局部变量,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度。

ThreadLocalMap@H_403_7@(线程的一个属性

1. @H_403_7@每个线程中都有一个自己的 ThreadLocalMap @H_403_7@类对象,可以将线程自己的对象保持到其中,

@H_403_7@各管各的,线程可以正确的访问到自己的对象。

2. @H_403_7@将一个共用的 ThreadLocal @H_403_7@静态实例作为 key@H_403_7@,将不同对象的引用保存到不同线程的

ThreadLocalMap @H_403_7@中,然后在线程执行的各处通过这个静态 ThreadLocal @H_403_7@实例的 get()@H_403_7@方法

@H_403_7@得自己线程保存的那个对象,避免了将这个对象作为参数传递的麻烦。

3. ThreadLocalMap @H_403_7@其实就是线程里面的一个属性,它在 Thread @H_403_7@类中定义

ThreadLocal.ThreadLocalMap threadLocals = null;

使@H_403_7@用场景

@H_403_7@最常见的 ThreadLocal @H_403_7@使用场景为 用来解决 数据库连接、Session @H_403_7@管理等。

 

 

 

18. synchronized ReentrantLock 的区别

@H_403_7@两者的共同点:

1. @H_403_7@都是用来协调多线程对共享对象、变量的访问

2. @H_403_7@都是可重入锁,同一线程可以多次获得同一个锁

3. @H_403_7@都保证了可见性和互斥性

@H_403_7@两者的不同点:

1. ReentrantLock @H_403_7@显示的获得、释放锁,synchronized @H_403_7@隐式获得释放锁

2. ReentrantLock @H_403_7@可响应中断、可轮回,synchronized @H_403_7@是不可以响应中断的,为处理锁的

@H_403_7@不可用性提供了更高的灵活性

3. ReentrantLock @H_403_7@是 API @H_403_7@级别的,synchronized @H_403_7@是 JVM @H_403_7@级别的

4. ReentrantLock @H_403_7@可以实现公平锁

5. ReentrantLock @H_403_7@通过 Condition @H_403_7@可以绑定多个条件

6. @H_403_7@底层实现不一样, synchronized @H_403_7@是同步阻塞,使用的是悲观并发策略,lock @H_403_7@是同步非阻

@H_403_7@塞,采用的是乐观并发策略

7. Lock @H_403_7@是一个接口,而 synchronized @H_403_7@是 Java @H_403_7@中的关键字,synchronized @H_403_7@是内置的语言

@H_403_7@实现。

8. synchronized @H_403_7@在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;

@H_403_7@而 Lock @H_403_7@在发生异常时,如果没有主动通过 unLock()@H_403_7@去释放锁,则很可能造成死锁现象,

@H_403_7@因此使用 Lock @H_403_7@时需要在 finally @H_403_7@块中释放锁。

9. Lock @H_403_7@可以让等待锁的线程响应中断,而 synchronized @H_403_7@却不行,使用 synchronized @H_403_7@时,

@H_403_7@等待的线程会一直等待下去,不能够响应中断。

10. @H_403_7@通过 Lock @H_403_7@可以知道有没有成功获取锁,而 synchronized @H_403_7@却无法办到。

11. Lock @H_403_7@可以提高多个线程进行读操作的效率,既就是实现读写锁等。

19. Java 中用到的线程调度

@H_403_7@抢占式调度

@H_403_7@抢占式调度指的是每条线程执行的时间、线程的切换都由系统控制,系统控制指的是在系统某种运行机制下,可能每条线程都分同样的执行时间片,也可能是某些线程执行的时间片较长,甚至某些线程得不到执行的时间片。在这种机制下,一个线程的堵塞不会导致整个进程堵塞。

@H_403_7@协同式调度

@H_403_7@协同式调度指某一线程执行完后主动通知系统切换到另一线程上执行,这种模式就像接力赛一样,一个人跑完自己的路程就把接力棒交接给下一个人,下个人继续往下跑。线程的执行时间由线程本身控制,线程切换可以预知,不存在多线程同步问题,但它有一个致命弱点:如果一个线程编写有问题,运行到一半就一直堵塞,那么可能导致整个系统崩溃。

 

JVM @H_403_7@的线程调度实现(抢占式调度)

java @H_403_7@使用的线程调使用抢占式调度,Java @H_403_7@中线程会按优先级分配 cpu @H_403_7@时间片运行,且优先级越高越优先执行,但优先级高并不代表能独自占用执行时间片,可能是优先级高得到越多的执行时间片,反之,优先级低的分到的执行时间少但不会分配不到执行时间。

@H_403_7@线程让出 cpu @H_403_7@的情况

1. @H_403_7@当前运行线程主动放弃 cpu@H_403_7@,JVM @H_403_7@暂时放弃 cpu @H_403_7@操作(基于时间片轮转调度的 JVM @H_403_7@操作系

@H_403_7@统不会让线程永久放弃 cpu@H_403_7@,或者说放弃本次时间片的执行权),例如调用 yield()@H_403_7@方法

2. @H_403_7@当前运行线程因为某些原因进入阻塞状态,例如阻塞在 I/O @H_403_7@上。

3. @H_403_7@当前运行线程结束,即运行完 run()@H_403_7@方法里面的任务。

20. 什么是 CAS(比较并交换-乐观锁机制-锁自旋)

@H_403_7@概念及特性

CAS@H_403_7@(Compare And Swap/Set@H_403_7@)比较并交换,CAS @H_403_7@算法的过程是这样:它包含 3 @H_403_7@个参数

CAS(V,E,N)@H_403_7@。V @H_403_7@表示要更新的变量(@H_403_7@内存值)@H_403_7@,E @H_403_7@表示预期值(@H_403_7@旧的)@H_403_7@,N @H_403_7@表示新值。当且仅当 V @H_403_7@值等于 E @H_403_7@值时,才会将 V @H_403_7@的值设为 N@H_403_7@,如果 V @H_403_7@值和 E @H_403_7@值不同,则说明已经有其他线程做了更新,则当前线程什么都不做。最后,CAS @H_403_7@返回当前 V @H_403_7@的真实值。

CAS @H_403_7@操作是抱着乐观的态度进行的(@H_403_7@乐观锁)@H_403_7@,它总是认为自己可以成功完成操作。当多个线程同时使用 CAS @H_403_7@操作一个变量时,只有一个会胜出,并成功更新,其余均会失败。失败的线程不会被挂起,仅是被告知失败,并且允许再次尝试,当然也允许失败的线程放弃操作。基于这样的原理,CAS @H_403_7@操作即使没有锁,也可以发现其他线程对当前线程的干扰,并进行恰当的处理。

@H_403_7@原子包 java.util.concurrent.atomic@H_403_7@(锁自旋)

JDK1.5 @H_403_7@的原子包:java.util.concurrent.atomic @H_403_7@这个包里面提供了一组原子类。其基本的特性就是在多线程环境下,当有多个线程同时执行这些类的实例包含的方法时,具有排他性,即当某个线程进入方法,执行其中的指令时,不会被其他线程打断,而别的线程就像自旋锁一样,一直等到该方法执行完成,才由 JVM @H_403_7@从等待队列中选择一个另一个线程进入,这只是一种逻辑上的理解。

@H_403_7@相对于对于 synchronized @H_403_7@这种阻塞算法,CAS @H_403_7@是非阻塞算法的一种常见实现。由于一般 cpu @H_403_7@切换时间比 cpu @H_403_7@指令集操作更加长, 所以 J.U.C @H_403_7@在性能上有了很大的提升。如下代码

 

 

 

 

getAndIncrement @H_403_7@采用了 CAS @H_403_7@操作,每次从内存中读取数据然后将此数据和+1 @H_403_7@后的结果进行CAS @H_403_7@操作,如果成功就返回结果,否则重试直到成功为止。而 compareAndSet @H_403_7@利用 JNI @H_403_7@来完成cpu @H_403_7@指令的操作。

ABA @H_403_7@问题

CAS @H_403_7@会导致“ABA @H_403_7@问题@H_403_7@。CAS @H_403_7@算法实现一个重要前提需要取出内存中某时刻的数据,而在下时刻比较并替换,那么在这个时间差类会导致数据的变化。

@H_403_7@比如说一个线程 one @H_403_7@从内存位置 V @H_403_7@中取出 A@H_403_7@,这时候另一个线程 two @H_403_7@也从内存中取出 A@H_403_7@,并且two @H_403_7@进行了一些操作变成了 B@H_403_7@,然后 two @H_403_7@又将 V @H_403_7@位置的数据变成 A@H_403_7@,这时候线程 one @H_403_7@进行 CAS @H_403_7@操作发现内存中仍然是 A@H_403_7@,然后 one @H_403_7@操作成功。尽管线程 one @H_403_7@的 CAS @H_403_7@操作成功,但是不代表这个过程就是没有问题的。

@H_403_7@部分乐观锁的实现是通过版本号(version@H_403_7@)的方式来解决 ABA @H_403_7@问题,乐观锁每次在执行数据的修改操作时,都会带上一个版本号,一旦版本号和数据的版本号一致就可以执行修改操作并对版本号执行+1 @H_403_7@操作,否则就执行失败。因为每次操作的版本号都会随之增加,所以不会出现 ABA @H_403_7@问题,因为版本号只会增加不会减少。

21. 什么是 AQS(抽象的队列同步器)

AbstractQueuedSynchronizer @H_403_7@类如其名,抽象的队列式的同步器,AQS @H_403_7@定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如常用的ReentrantLock/Semaphore/CountDownLatch@H_403_7@。

 

@H_403_7@它维护了一个 volatile int state@H_403_7@(代表共享资源)和一个 FIFO @H_403_7@线程等待队列(多线程争用资源被

@H_403_7@阻塞时会进入此队列)。这里 volatile @H_403_7@是核心关键词,具体 volatile @H_403_7@的语义,在此不述。state @H_403_7@的

@H_403_7@访问方式有三种:

getState()

setState()

compareAndSetState()

AQS @H_403_7@定义两种资源共享方式

xclusive @H_403_7@独占资源-ReentrantLock

Exclusive@H_403_7@(独占,只有一个线程能执行,如 ReentrantLock@H_403_7@)

Share @H_403_7@共享资源-Semaphore/CountDownLatch

Share@H_403_7@(共享,多个线程可同时执行,如 Semaphore/CountDownLatch@H_403_7@)。

AQS @H_403_7@只是一个框架,具体资源的获取/@H_403_7@释放方式交由自定义同步器去实现,AQS @H_403_7@这里只定义了一个接口,具体资源的获取交由自定义同步器去实现了(通过 state @H_403_7@的 get/set/CAS)@H_403_7@之所以没有定义成abstract @H_403_7@,是因为独占模式下只用实现 tryAcquire-tryRelease @H_403_7@,而共享模式下只用实现tryAcquireShared-tryReleaseShared@H_403_7@。如果都定义成abstract@H_403_7@,那么每个模式也要去实现另一模式下的接口。不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现共享资源 state @H_403_7@的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/@H_403_7@唤醒出队等),AQS @H_403_7@已经在顶层实现好了。自定义同步器实现时主要实现以下几种方法

1@H_403_7@.isHeldExclusively()@H_403_7@:该线程是否正在独占资源。只有用到 condition @H_403_7@才需要去实现它。

2@H_403_7@.tryAcquire(int)@H_403_7@:独占方式。尝试获取资源,成功则返回 true@H_403_7@,失败则返回 false@H_403_7@。 3@H_403_7@.tryRelease(int)@H_403_7@:独占方式。尝试释放资源,成功则返回 true@H_403_7@,失败则返回 false@H_403_7@。 4@H_403_7@.tryAcquireShared(int)@H_403_7@:共享方式。尝试获取资源。负数表示失败;0 @H_403_7@表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。

5@H_403_7@.tryReleaseShared(int)@H_403_7@:共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true@H_403_7@,否则返回 false@H_403_7@。

@H_403_7@同步器的实现是 ABS @H_403_7@核心(state @H_403_7@资源状态计数)

@H_403_7@同步器的实现是 ABS @H_403_7@核心,以 ReentrantLock @H_403_7@为例,state @H_403_7@初始化为 0@H_403_7@,表示未锁定状态。A @H_403_7@线程lock()@H_403_7@时,会调用 tryAcquire()@H_403_7@独占该锁并将 state+1@H_403_7@。此后,其他线程再 tryAcquire()@H_403_7@时就会失败,直到 A @H_403_7@线程 unlock()@H_403_7@到 state=0@H_403_7@(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A @H_403_7@线程自己是可以重复获取此锁的(state @H_403_7@会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证 state @H_403_7@是能回到零态的。

@H_403_7@以 CountDownLatch @H_403_7@以例,任务分为 N @H_403_7@个子线程去执行,state @H_403_7@也初始化为 N@H_403_7@(注意 N @H_403_7@要与线程个数一致)。这 N @H_403_7@个子线程是并行执行的,每个子线程执行完后 countDown()@H_403_7@一次,state@H_403_7@会 CAS @H_403_7@减 1@H_403_7@。等到所有子线程都执行完后(@H_403_7@即 state=0)@H_403_7@,会 unpark()@H_403_7@主调用线程,然后主调用线程就会从 await()@H_403_7@函数返回,继续后余动作。

ReentrantReadWriteLock @H_403_7@实现独占和共享两种方式

 @H_403_7@一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现 tryAcquiretryRelease@H_403_7@、tryAcquireShared-tryReleaseShared @H_403_7@中的一种即可。但 AQS @H_403_7@也支持自定义同步器同时实现独占和共享两种方式,如 ReentrantReadWriteLock@H_403_7@。

原文链接:/Javainterview/997434.html

猜你在找的Java面试题相关文章