java中的各种锁性能比较及原理
多线程的缘由
进程间的切换耗费的代价太大,因此需要一种花销小,切换快的多任务操作方式。
一个进程中可以同时运行多个线程,彼此之间使用相同的地址空间,共享大部分数据。启动一个线程所花费的空间远小于启动一个进程所花费的空间。
由于多个线程共享所属进行的资源和地址空间,那么当多线程要同时访问某个资源时就存在加锁的问题。
常见的锁有:
- synchronized
- ReentrantLock
- Semaphore
- AtomicInteger
synchronized
synchronized机制是给共享资源上锁,只有拿到锁的线程才可以访问共享资源,这样就可以强制使得对共享资源的访问都是顺序的。synchronized实现的机理依赖于软件层面上的JVM。
在多线程应用程序中使用该关键字,实现方便,后续工作由JVM来完成,可靠性高。只有在确定锁机制是当前多线程程序的性能瓶颈时,才考虑使用其他机制,如ReentrantLock等
ReentrantLock
可重入锁,顾名思义,这个锁可以被线程多次重复进入进行获取操作。
ReentantLock继承接口Lock并实现了接口中定义的方法,除了能完成synchronized所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法。
Lock实现的机理依赖于特殊的CPU指定,可以认为不受JVM的约束,并可以通过其他语言平台来完成底层的实现。在并发量较小的多线程应用程序中,ReentrantLock与synchronized性能相差无几,但在高并发量的条件下,synchronized性能会迅速下降几十倍,而ReentrantLock的性能却能依然维持一个水准。
与synchronized会被JVM自动解锁机制不同,ReentrantLock加锁后需要手动进行解锁。为了避免程序出现异常而无法正常解锁的情况,使用ReentrantLock必须在finally控制块中进行解锁操作。
Semaphore
上述两种锁机制类型都是“互斥锁”,学过操作系统的都知道,互斥是进程同步关系的一种特殊情况,相当于只存在一个临界资源,因此同时最多只能给一个线程提供服务。但是,在实际复杂的多线程应用程序中,可能存在多个临界资源,这时候我们可以借助Semaphore信号量来完成多个临界资源的访问。
Semaphore基本能完成ReentrantLock的所有工作,使用方法也与之类似,通过acquire()与release()方法来获得和释放临界资源。
Semaphore的锁释放操作也由手动进行,因此与ReentrantLock一样,为避免线程因抛出异常而无法正常释放锁的情况发生,释放锁的操作也必须在finally代码块中完成。
AtomicInteger
i++等运算不具有原子性,是不安全的线程操作之一。JVM为此类操作特意提供了一些同步类,使得使用更方便,且使程序运行效率变得更高通过相关资料显示,通常AtomicInteger的性能是ReentantLock的好几倍。