Java高并发之volatitle、sychronized、Lock接口、AQS(三种核心方法、重写方法、模板方法)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/u014454538/article/details/97620717

1. volatitle和synchronized关键字

① volatitle关键字
  • 对共享变量进行准确和一致的更新时,线程需要通过排它锁单独获得这个共享变量。
  • 使用排他锁的代价是非常大的,我们使用volatitle来保证共享变量的可见性
  • 可见性: 一个线程修改共享变量时,其他线程能获得共享变量修改后的值。
  • 定义为volatitle的共享变量具有的特性:
  1. 线程之间的可见性
  2. 允许多个线程同时读,而且读到的值不过期
  3. 只允许单线程写
  • volatitle是轻量级的synchronized,不会引起线程的上下文切换和调度。
  • volatitle的优化:LinkedTransferQueue中,使用volatile变量时,使用一种追加字节的方式优化队列出队和入队的性能。
② synchronized关键字
  • synchronized实现同步的基础:Java中的每一个对象都可以作为锁
  1. 对于普通同步方法,锁是当前实例对象
  2. 对于静态同步方法,锁是当前对象的 Class 对象
  3. 对于同步方法块,锁是 Synchonized 括号里配置的对象
  • synchronized的实现的过程:
  1. 通过进入和退出Monitor对象实现方法同步和代码块同步。
  2. 代码块的同步使用monitorenter指令monitorexit指令实现,monitorenter指令编译后插入到同步代码块的开始处monitorexit指令编译后插入同步代码块的结束处和异常处
  3. 每个对象都有monitor与之关联,线程执行到monitorenter指令时,需要获取monitor的持有权,即获取对象的锁;线程执行到monitorexit指令时,需要释放monitor的持有权,即释放锁
  • synchronized的存储位置
  • 锁存在 Java 对象头里,如果对象是数组类型,则虚拟机用 3 个 Word(字宽)存储对象头;如果对象是非数组类型,则用 2 字宽存储对象头。
  • 在 32 bit虚拟机中,一字宽(Word)等于四字节,即 32bit。64 bit的虚拟机,word则是64 bit。
长度 内容 说明
32 bit/64 bit Mark Word 存储对象的hashCode、分代年龄、锁等信息
32 bit/64 bit Class Metadata Address 存储到对象类型数据的指针(对象的地址)
32 bit/64 bit Array Length 存储数组长度(数组类型的对象才有)

2. Lock锁

① Lock锁的概述
  • JVM使用synchronized关键字实现锁功能,隐式地获取和释放锁,给Java编程人员带来了一定的便捷性。却又增加了一些不便性: 如果需要手把手的获取和释放锁,就难以使用synchronized关键字实现。
  • 手把手获取和释放锁的场景:获取锁A ----> 获取锁B ----> 释放锁A同时获取锁C ----> 释放锁B同时获取锁D,以此类推。
  • Java SE 5之后,在Java的并发包(java.util.concurrentJUC)中提供了Lock接口及其实现类,用于实现锁功能。
  • 与synchronized关键字相比,Lock接口需要显式地获取和释放锁,拥有获取和释放锁的可操作性可中断的获取锁超时获取锁等synchronized关键字不具有的同步特性。
  • Lock接口相对于synchronized关键字所具有的主要特性:
  1. 尝试非阻塞的获取锁: 当前线程尝试获取锁,如果同一时刻没有其他线程获取到该锁,则获取锁成功,返回true;否则,获取锁失败,返回false。
  2. 可中断的获取锁: 如果当前线程使用可中断的方式获取锁,在获取锁的过程中可以响应中断,抛出中断异常。
  3. 超时获取锁: 当前线程尝试获取锁,如果不能立即获取锁则等待指定的时间。超时仍未获取到锁,则返回。
  • 如何使用Lock接口:
Lock lock=new ReentrantLock();
lock.lock(); // 获取锁
try{
    ...
}finally {
    lock.unlock(); // 释放锁
}
  1. 在try语句之前获取,如果在try语句中获取锁时发生了异常,则会抛出异常并自动释放锁。
  2. 在finally语句块中释放锁,保证无论程序正常运行结束、还是发生异常退出,都会执行finally语句块中的释放锁操作。
② Lock接口的API
  • Lock是一个接口,定义了获取和释放锁的基本操作。
public interface Lock {
    void lock();
    void lockInterruptibly() throws InterruptedException;
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    void unlock();
    Condition newCondition();
}

在这里插入图片描述

  • lock()方法: 获取锁(阻塞地获取锁)。如果该锁已被其他线程获取,则当前线程进行等待,直到获得锁后返回。
  • lockInterruptibly()方法: 可中断的获取锁。与lock()方法不同的是,当前线程在获取的过程可以响应中断,抛出中断异常。
  • tryLock()方法: 尝试非阻塞的获取锁。如果当前线程能立即获取锁,则返回true;否则,返回false
Lock lock = new ReentrantLock();
if (lock.tryLock()) {
  try {
  	// manipulate protected state
  } finally {
  	lock.unlock();
  }
} else {
  // perform alternative actions
}
  • tryLock(long time, TimeUnit unit)方法: 超时获取锁。如果获取到锁,返回true;否则,返回false。与tryLock()方法相比,如果当前线程不能立即获取锁,则进入超时等待。有以下3种情况之一,该线程才能返回:
  1. 超时等待的过程中,当前线程获取到了锁。
  2. 超时等待的过程中,当前线程被中断。
  3. 超时等待结束,当前线程仍未获取到锁。
  • unlock()方法: 释放锁。
  • newCondition()方法: 获取锁的Condition对象,调用condition.await()方法当前线程进入等待状态,直到调用condition.signal()方法或发生中断才返回。

3. 队列同步器(AQS)

① AQS概述
  • 队列同步器(AbstractQueuedSynchronizer,AQS),以下简称同步器,是实现锁或其他同步组件的基础框架
  • 同步器的两个核心: 使用int成员变量表示的同步状态、具有FIFO顺序的同步队列
  • 同步状态的获取和释放有两种方式:独占式的获取和释放同步状态、共享式的释放和获取同步状态。
  • 利用同步器实现同步组件的方式,以实现Lock接口的锁为例:
  1. 创建同步器的子类,并且重写同步器中的指定方法,如tryAcquire()tryRelease()isHeldExclusively()注意: 同步器的子类一般都作为同步组件的静态内部类private static class)。
  2. 创建Lock接口的实现类,调用同步器的模板方法重写Lock接口中的方法(lock()lockInterruptibly()tryLock()tryLock(time, unit)unlock()newCondition()等主要方法)。
  3. 同步器的模板方法会调用重写方法,而重写方法会通过getState()setState(int newState)compareAndSetState(int expect, int update),这三种方法访问或更新同步状态
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 Mutex implements Lock {
    //静态内部类,自定义同步器
    private static class Sync extends AbstractQueuedSynchronizer{
        //是否处于占用状态
        protected boolean isHeldExclusively(){
            return getState() == 1;
        }
        //当状态为0时获取锁
        protected boolean tryAcquire(int arg){
            if(compareAndSetState(0,1)){
                setExclusiveOwnerThread(Thread.currentThread());    //设置成功,则代表获取了同步状态。
                return true;
            }
            return false;
        }
        //释放锁,将状态设置为0
        protected boolean tryRelease(int releases){
            if(getState()==0)
                throw new IllegalMonitorStateException();
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }
        //返回一个Condition,每个condition都包含了一个condition队列
        Condition newCondition(){
            return new ConditionObject();
        }
    }
    //仅仅需要将操作代理到Sync上即可
    private final Sync sync = new Sync();
    public boolean isLocked(){
        return sync.isHeldExclusively();    //是否处于同步状态
    }
    @Override
    public void lock() {
        sync.acquire(1);    //获取锁,将状态设置为1
    }
    @Override
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }
    @Override
    public boolean tryLock() {
        return sync.tryAcquire(1);//尝试获取锁
    }
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1,unit.toNanos(1000));
    }
    @Override
    public void unlock() {
        sync.release(1);    //释放锁
    }
    @Override
    public Condition newCondition() {
        return sync.newCondition();
    }
}
② 同步器的三种核心方法
  • 同步器中使用int变量表示同步状态,提供三种核心方法访问或更新同步状态。重写同步器的指定方法,需要调用这三种核心方法来访问或更新同步状态。
  1. int getState(): 获取同步状态的当前值
  2. void setState(int newState): 设置同步状态的值
  3. boolean compareAndSetState(int expect, int update): 使用CAS原子性地设置同步状态的值,当同步状态的当前值为expect时,将其设置为给定值(update)。
③ 同步器的可重写方法
  • 同步器的可重写方法主要包括:独占式的获取或释放同步状态共享式的获取或释放同步状态同步器是否在独占模式下被线程占用
  1. boolean tryAcquire(int arg): 独占式的获取同步状态,实现该方法需要先获取并判断同步状态是否符合预期,再通过CAS设置同步状态。(即使用compareAndSetState(expect, update)方法)。可用于实现Lock接口中的tryLock()方法。
  2. boolean tryRelease(int arg): 独占式的释放同步状态,等待获取同步状态的线程将有机会获取同步状态。
  3. int tryAcquireShared(int arg): 共享式的获取同步状态,返回大于等于0的值表示获取成功,否则获取失败。
  4. boolean tryReleaseShared(int arg): 共享式的释放同步状态
  5. boolean isHeldExclusively(): 用于获取同步器是否在独占模式下被线程占用,一般用来表示是否被当前线程所独占
④ 同步器中的模板方法
  • 实现自定义的同步组件,需要调用同步器中模板方法,而模板方法内部则调用的是同步器的可重写方法
  1. void acquire(int arg): 独占式的获取同步状态,会调用tryAcquire()重写方法。如果当前线程获取同步状态成功,则直接返回;否则,进入同步队列等待。
  2. void acquireInterruptibly(int arg):acquire()相同,但可以响应中断。如果当前线程获取同步状态失败,进入同步队列,直到获取成功或响应中断。如果响应中断,会抛出中断异常(InterruptException)。
  3. boolean tryAcquireNanos(int arg, long nanosTimeout):acquireInterruptibly()的基础上增加了等待时间,如果等待超时,则返回false;如果在等待的时间内,成功获取则返回true
  4. boolean release(int arg): 独占式的释放同步状态,并在释放同步状态之后,唤醒同步队列中第一个节点包含的线程
  5. void acquireShared(int arg): 共享式的获取同步锁,与独占式获取的主要区别:同一时刻可以有多个线程获取到同步状态
  6. void acquireSharedInterruptibly(int arg):acquireShared()相同,但可以响应中断。
  7. boolean tryAcquireSharedNanos(int arg, long nanosTimeout):acquireSharedInterruptibly()的基础上增加了等待时间。
  8. boolean releaseShared(int arg): 共享式的释放同步状态
  9. Collection getQueuedThreads(): 获取在同步队列中等待的线程集合。

猜你喜欢

转载自blog.csdn.net/u014454538/article/details/97620717