带你了解Java中各种锁的概念

Java中各种锁的概念

线程安全/线程不安全

悲观锁/乐观锁

这两个锁是思想上定义出的概念,大致理解其中的意思即可;

悲观锁 总是持悲观的态度,每次去操作数据的时候总会认为别的线程会去修改;所以在每次拿之前进行锁定,其他线程则一致等待直到它自己拿到锁。 共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程 典型示例:synchronized 乐观锁 总是持乐观的态度,每次去操作数据的时候总会认为别的线程不会去修改;从而一开始不进行上锁,但是再准备修改数据的共享资源的时候,会先判断数据有没有更新,然后再继续操作;常用方式根据版本号控制,或者CAS无锁算法 乐观不是完全乐观,只不过是与悲观锁相比,悲观锁先锁再判定再更新;而乐观锁是先判定再判定是否锁再更新 典型示例:Java中java.util.concurrent.atomic包下面的原子变量类

总结 乐观锁适用于读操作比较多的场景 悲观锁适用于写操作比较多的场景

独享锁/共享锁

独享锁 独自享有,指锁每次有且只能被一个线程占有 典型示例:ReentrantLock 共享锁 共同享有,指锁每次允许多个线程占有 典型示例:Semaphore、CountDownLatch、ReentrantReadWriteLock里的读锁,它的读锁是可以被共享的,但是它的写锁每次只能被读占,读锁的共享可保证并发读是非常高效的,但是读写和写写,写读都是互斥的

互斥锁/读写锁

互斥锁 互斥意味着一个锁某一时刻只能被一个线程持有,其它试图获取锁的线程都会被阻塞,直至当前锁释放,该锁上的其它线程进入就绪状态,准备抢占锁 典型示例:synchronized、Lock 读写锁 读写锁既是互斥锁,又是共享锁,read模式是共享,write模式是互斥(排他锁) 典型示例:ReentrantReadWriteLock

公平锁/非公平锁

公平锁 是指多个线程按照申请锁的顺序来获取锁。 典型示例:new ReentrantLock(true) 非公平锁 不是按照顺序来获取锁,这意味着它存在这线程饥饿或优先级反转问题。 典型示例:new ReentrantLock(false)

偏向锁/轻量级锁/重量级锁

这几个概念是jdk1.5版本提出的,为了优化synchronized提升效率,这三种锁的状态是通过对象监视器在对象头中的字段来表明的。

偏向锁 是指锁一直被一个线程持有,再没有其它线程竞争该锁的情况下,当前线程可自由获取该锁,降低了获取锁的代价。

轻量级锁 当锁处于偏向锁状态时,这时候有其它线程来尝试获取该锁,则发生了竞争态势,该锁会从偏向升级为轻量级锁,其它线程会通过自旋来尝试获取锁,不会阻塞从而提高效率。不过需要注意的是,轻量级锁并不是用来代替重量级锁的,它是状态的一种过渡。

重量级锁 当锁处于轻量状态的时候,其它线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。这就是jdk1.5之前为什么称synchronized为重量级锁的原因,它会使其它线程直接进入阻塞状态,从而非常影响性能,1.5版本开始提出这些锁的状态,改变了synchronized重量级的包袱。

可重入锁

所谓重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的,它的意义在于防范死锁 典型示例:synchronized

自旋锁

是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。 典型示例:CAS锁算法

死锁

死锁产生的原因是等待需要的资源而一直拿不到,就会导致线程一直处于等待中。比较专业的定义是:一组互相竞争资源的线程因互相等待,导致“永久”阻塞的现象。

分布式锁

很多时候我们需要保证一个方法在同一时间内只能被同一个线程执行。在单机环境中可以很容易解决,但是在分布式环境下就比较复杂了。此时就引入了分布式锁的概念 典型示例:redis实现分布式锁