并发(五、锁 Lock)

1、Lock

java.util.concurrent 包简称 JUC,Lock 就在这个包下的 locks 包下。包含如下方法:

  • void lock():获取锁
  • void lockInterruptibly():可中断的获取锁
  • boolean tryLock():尝试非阻塞的获取锁,立即返回boolean
  • boolean tryLock(long time, TimeUnit unit):功能同上,超时的获取锁
  • void unlock():释放锁
  • Condition newCondition():这是一个获取等待通知的组件

2、CAS

CAS(Compare And Swap)比较并交换 是一种原子操作,用于实现多线程环境下的同步操作。 CAS 操作包含三个操作数:内存位置(V)、旧的预期值(A)和新值(B)。当且仅当预期值 A 和内存位置 V 的值相同时,CAS 会将内存位置 V 的值更新为新值 B。

主要包含以下三个步骤:

  1. 读取:读取内存位置V的值。
  2. 比较:将读取到的值与期望的原值A进行比较。
  3. 交换:如果读取到的值等于A,则将内存位置V的值设置为新值B;否则开始 自旋(Spinning)来持续尝试重新执行CAS操作,直到成功为止。

无锁的概念:

  • 乐观派:无锁,总是认为对临界资源的访问没有冲突。一旦发生冲突则采用 CAS 技术来保证线程安全。这就是乐观锁。
  • 悲观派:加锁,总是认为对临界资源的访问存在冲突。synchronized 就是悲观锁。

◎ CAS 实现的基石:Unsafe 类

CAS 想要保证线程是安全的,一个实现的关键就是要保证 比较并交换 是一个原子操作。

在 Java 里面就是用 Unsafe 类来实现 CAS 的原子操作,它通过 JNI(Java Native Interface;Java本地接口) 方式去访问本地的 C++库,从而使得 Java 拥有能够直接操作内存空间的能力。

◎ CAS 在 Java 中的应用

比如:java.util.concurrent.atomic 包、java.util.concurrent.locks 包下面的一系列的类。

在 JDK1.6+,synchronized 升级为重量级锁之前,也是采用的 CAS 机制。

◎ CAS 的经典三大问题

  1. CAS 采用自旋的方式,循环性能开销大,浪费CPU资源
    1. 优化方案:在Java中,很多使用自旋CAS的地方,会有一个自旋次数的限制,超过一定次数,就停止自旋。
  2. 不能保证代码块的原子性,只能保证一个变量的原子性操作
    1. 优化方案1:可以考虑改用 juc 包中的锁来保证操作的原子性。
    2. 优化方案2:使用原子类,或可以考虑合并多个变量,将多个变量封装成一个对象,通过 AtomicReference 来保证原子性。
    3. 优化方案3:使用 asynchronized
  3. ABA问题:并发环境下,假设初始条件是A,去修改数据时,发现是A就会执行修改。但是看到的虽然是A,中间可能发生了A变B,B又变回A的情况。此时A已经非彼A,数据即使成功修改,也可能有问题。

怎么解决 ABA问题?加版本号

每次修改变量,都在这个变量的版本号上加1,这样,刚刚A->B->A,虽然A的值没变,但是它的版本号已经变了,再判断版本号就会发现此时的A已经被改过了。参考乐观锁的版本号,这种做法可以给数据带上了一种实效性的检验。

Java提供了 AtomicStampReference 类,它的 compareAndSet 方法首先检查当前的对象引用值是否等于预期引用,并且当前印戳(Stamp)标志是否等于预期标志,如果全部相等,则以原子方式将引用值和印戳标志的值更新为给定的更新值。


3、AQS

AbstractQueuedSynchronizer 抽象同步队列,简称 AQS ,它是 Java并发包的根基,并发包中的锁就是基于AQS实现的。

AQS能干什么?

  1. 同步队列的管理和维护
  2. 同步状态管理
  3. 线程阻塞、唤醒管理

独占锁:也叫互斥锁、排它锁,就是一个线程获得了锁,那么其他想要获取这把锁的线程就得等待。

共享锁:可以多个线程同时获取的锁,典型的如:读/写锁里的读锁。

  • AQS是一个一个 FIFO 的双向队列(基于 CLH 队列实现),Node 节点中的 thread 变量用来存放进入 AQS队列里的线程,SHARED 表示是获取共享资源时被阻塞挂起后放入 AQS队列的, EXCLUSIVE 表示是获取独占资源时被挂起后放入 AQS队列的。
  • AQS 使用一个 volatile 修饰的 int 类型的成员变量 state 来表示同步状态,修改同步状态成功即为获得锁,volatile 保证了变量在多线程之间的可见性,修改 state 值时通过 CAS 机制来保证修改的原子性。
  • 获取 state 的方式分为两种,独占方式 tryAcquire() 和共享方式 tryAcquireShared(), 一个线程使用独占方式获取了资源,其它线程就会在获取失败后被阻塞。一个线程使用共享方式获取了资源,另外一个线程还可以通过 CAS 的方式进行获取,tryAcquire()tryAcquireShared() 是抽象方法,需要子类自行实现。
  • 如果共享资源被占用,需要一定的阻塞等待唤醒机制来保证锁的分配,AQS 中会将竞争共享资源失败的线程添加到队列中,线程进入队列后会进行自旋,自旋一定次数后,会使用 LockSupport.park() 进入阻塞状态。
  • 获取到锁的线程可以重入,每重入一次,state+1,释放资源的时候,会使用 CAS 操作将 state 修改为 0,重入多少次,释放多少次,并使用 LockSupport.unpark() 唤醒处于等待状态的线程。

CLH 是什么:AQS 是 CLH(Craig、Landin、Hagersten)锁定队列的变体。CLH队列结构如下:

在这里插入图片描述

● 用法

使用这个类用作同步的基础上,重新定义以下方法,如适用,通过检查和/或修改使用所述同步状态 getState()setState(int) 和/或 compareAndSetState(int, int)

● 独占锁和共享锁实现差异

▼ 出队差异

正常情况下,独占锁,只有持有锁的线程运行结束了,释放锁后独占锁的节点才会出队。

共享锁,是在唤醒了下一个节点,并且被唤醒的节点成功被设置为 head 节点后,自己这个节点就出队了。

▼ 唤醒差异

独占锁,只有在释放锁的时候,才会去看看要不要唤醒下一个节点。

共享锁,是在两个地方去看看是不是要唤醒下一个节点;一个是在获取锁的过程中调用 setHeadAndPropagate 方法的时候,另一个是释放锁的过程中调用 unparkSuccessor 方法的时候.


4、Demo 锁

4.1、synchronized 实现锁(Low)

1、简单锁

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class MyLock implements Lock {
   

    private boolean hasLocked = false;

    @Override
    public synchronized void lock() {
   
        while(hasLocked){
   
            try {
   
                wait();
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
        }

        if(!hasLocked)
            hasLocked = true;
    }

    @Override
    public synchronized void unlock() {
   
        if(hasLocked) {
   
            hasLocked = false;
            notifyAll();
        }
    }
    
    // 其余 Override 略(主要是没写)
}

2、可重入锁

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class MyReentrantLock implements Lock {
   

    private boolean hasLocked = false;
    private Thread current = null;
    private int count = 0;

    @Override
    public synchronized void lock() {
   
        Thread entryThread = Thread.currentThread();
        while(hasLocked && !entryThread.equals(current)){
   
            try {
   
                wait();
            } catch (InterruptedException e) {
   
                e.printStackTrace();
            }
        }

        if(!hasLocked) {
   
            hasLocked = true;
            current = entryThread;
            count++;
        } else if (entryThread.equals(current)){
   
            count++;
        }
    }

    @Override
    public synchronized void unlock() {
   
        if(hasLocked) {
   
            if(Thread.currentThread().equals(current)){
   
                count--;
                if(count == 0){
   
                    hasLocked = false;
                    current = null;
                    notifyAll();
                }
            }
        }
    }
    
    // 其余 Override 略(主要是没写)
}

4.2、AQS 实现锁

1、简单锁

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

/**
 * 不可重入锁
 */
public class MyLock implements Lock {
   

    private final Sync sync = new Sync();

    private class Sync extends AbstractQueuedSynchronizer {
   

        // 尝试以独占模式获取锁
        public boolean tryAcquire(int acquires){
   
            assert acquires == 1;
            if(compareAndSetState(0, 1)) {
   
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        // 尝试释放锁(该方法总是由执行释放的线程调用)
        protected boolean tryRelease(int releases) {
   
            assert releases == 1;
            if(!isHeldExclusively()){
   
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        public boolean isLocked() {
   
            return getState() != 0;
        }

        // 返回true如果同步仅针对当前(调用)线程进行保存
        public boolean isHeldExclusively() {
   
            return getExclusiveOwnerThread() == Thread.currentThread();
        }

        public Condition newCondition() {
   
            return new ConditionObject();
        }

    }

    @Override
    public void lock() {
   
        sync.acquire(1);
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {
   
        sync.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
   
        return sync.tryAcquire
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

纯纯的小白

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值