Java中的各种锁

一、概述

锁的思维导图:
在这里插入图片描述

二、synchronized

  • synchronized 是可重入锁,Lock的实现类都是可重入锁;
  • synchronized 是不可中断锁,而Lock的实现类都是可中断锁;
  • synchronized 是一种非公平锁,Lock有FairSync(公平锁)和NonFairSync(非公平锁);
  • synchronized 属于互斥锁,任何时候只允许一个线程的读写操作,其他线程必须等待;而Lock中的ReadWriteLock允许多个线程获得读锁。

使用:

	// 可锁方法,也可锁类
    private static synchronized void addCount() {
        count++;
    }

三、CAS(比较和交换)

CAS, 英文直译为 compare and swap,即比较和交换。乐观锁其实就是一种比较与交换的过程。

简单描述一下就是:读取到一个值为 A ,在要将这个值更新为B 之前,检查是否等于 A (比较),如果是则将 A 更新为 B(交换) ,否则什么都不做。

通过这种方式,可以实现不必使用加锁的方式,就能保证资源在多线程之间的同步,显然,不阻塞线程,可以大大提高吞吐量。方式虽好,但是也存在问题。

  • ABA 问题,即如果一个值从 A 变为 B 再变回 A 时,这样 CAS 就会认为值没有发生变化。对于这个问题,已经有了使用版本号的解决方式,即每次变量更新的时候变量的版本号都 +1,即由 A->B->A 就变成了 1A->2B->3A 。
  • 循环时间长开销大,如果锁的竞争比较激烈,就会导致 CAS 不断的重复执行,一直循环,耗费 CPU 资源。
  • 只能保证一个变量的同步,显然,由于其特性,CAS 只能保证一个共享变量的原子操作。

JDK 中对 CAS 的实现在 java.util.concurrent.atomic 包中:
在这里插入图片描述
可以用原子方式更新其值。

四、AQS

AQS,全名 AbstractQueuedSynchronizer,直译为抽象队列同步器,是构建锁或者其他同步组件的基础框架,可以解决大部分同步问题。实现原理可以简单理解为:同步状态( state ) + FIFO 线程等待队列

  • 资源 state
    AQS使用了一个 int 类型的成员变量 state 来表示同步状态,使用了 volatile 关键字来保证线程间的可见性,当 state > 0 时表示已经获取了锁,当 state = 0 时表示释放了锁。它提供了三个方法(getState()、setState(int newState)、compareAndSetState(int expect,int update))来对同步状态state进行操作,确保对state的操作是安全的。
    ❀ 而对于不同的锁,state 也有不同的值:
  • 独享锁中 state =0 代表释放了锁,state = 1 代表获取了锁。
  • 共享锁中 state 即持有锁的数量。
  • 可重入锁 state 即代表重入的次数。
  • 读写锁比较特殊,因 state 是 int 类型的变量,为 32 位,所以采取了中间切割的方式,高 16 位标识读锁的数量 ,低 16 位标识写锁的数量 。
  • FIFO 线程等待队列
    实现队列的方式无外乎两种,一是使用数组,二是使用 Node 。AQS 使用了 Node 的方式实现队列。

FIFO 线程等待队列的结构如下图:
在这里插入图片描述
列举两个方法使用(还有更多其他方法):

// 独占模式获取锁,忽略中断。
acquire(int arg)

// 以独占模式释放对象。
release(int arg)

五、Lock

Java中的Lock:
在这里插入图片描述

  • ReentrantLock:可重入锁
  • ReentrantReadWriteLock :读写锁,允许多个线程获得读锁,但只允许一个线程获得写锁,效率相对较高。(读读✔,读写×,写读×,写写×)
  • StampedLock:对ReentrantReadWriteLock锁的增强方法,解决ReentrantReadWriteLock在读写分离时的线程饥饿问题。当ReentrantReadWriteLock对其写锁想要获取的话,就必须没有任何其他读写锁存在才可以,这实现了悲观读取。如果读操作很多,写很少的情况下,线程有可能就会遭遇饥饿问题;(乐观读时,写✔)

StampedLock的三种模式
1、写入(Writing):writeLock是一个独占锁,也是一个悲观锁。
2、读取(Reading):readLock这时候是一个悲观锁。
3、乐观读取(Optimistic Reading):提供了tryOptimisticRead方法返回一个非0的stamp,只有当前同步状态没有被写模式所占有是才能获取到。他是在获取stamp值后对数据进行读取操作,最后验证该stamp值是否发生变化,如果发生变化则读取无效,代表有数据写入。这种方式能够降低竞争和提高吞吐量。

StampedLock使用示例:

    public static void read() {
        //获取乐观锁,并返回stamp值,该方法不会使writeLock阻塞
        long stamp = lock.tryOptimisticRead();
        //读取数据
        System.out.println("normal R-> " + stamp + "==" + list.size());
        //判断stamp值是否发生变化
        if (!lock.validate(stamp)) {
            //内容被修改,重新获取
            try {
                stamp = lock.readLock();
                System.out.println("data change R-> " + stamp + "==" + list.size());
                TimeUnit.MILLISECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlockRead(stamp);
            }
        }
    }

锁的实现原理可看:https://segmentfault.com/a/1190000023735772

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java有多种,可以根据应用场景的不同选择不同的。 1. synchronized:synchronized是Java最基本的。它是一种互斥,同一时间只能有一个线程持有该,其他线程必须等待该线程释放才能获得。synchronized可以应用于方法或代码块。 2. ReentrantLock:ReentrantLock是Java的另一种互斥,与synchronized相比,它的灵活性更高,可以指定公平或非公平的获取方式。在高并发环境下,ReentrantLock的性能可能比synchronized更好。但是,它需要手动释放,否则可能会导致死。 3. ReadWriteLock:ReadWriteLock是Java的一种读写,它允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。在读多写少的场景下,使用ReadWriteLock可以提高程序的并发性能。 4. StampedLock:StampedLock是Java8新增的一种,它比ReadWriteLock更加灵活,支持乐观读取和写,可以大幅度提高程序的并发性能。但是,StampedLock的使用要求比较高,需要仔细考虑的使用场景。 5. CAS:CAS(Compare And Swap)是Java的一种无算法,它通过比较内存的值与期望值是否相等来判断是否发生了竞争。如果没有竞争,就将新值写入内存;如果有竞争,则重新尝试。CAS的性能非常高,但是它对程序员的要求也非常高,需要处理好竞争和冲突问题。 6. Semaphore:Semaphore是Java的一种信号量,它可以控制同时访问某个资源的线程数量。Semaphore可以应用于控制并发访问的线程数量、线程池的大小等场景。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值