【22】AQS底层原理、运用和volatile详解

(1)一个人只要自己不放弃自己,整个世界也不会放弃你.
(2)天生我才必有大用
(3)不能忍受学习之苦就一定要忍受生活之苦,这是多么痛苦而深刻的领悟.
(4)做难事必有所得
(5)精神乃真正的刀锋
(6)战胜对手有两次,第一次在内心中.
(7)好好活就是做有意义的事情.
(8)亡羊补牢,为时未晚
(9)科技领域,没有捷径与投机取巧。
(10)有实力,一年365天都是应聘的旺季,没实力,天天都是应聘的淡季。
(11)基础不牢,地动天摇
(12)写博客初心:成长自己,辅助他人。当某一天离开人世,希望博客中的思想还能帮人指引方向.
(13)编写实属不易,若喜欢或者对你有帮助记得点赞+关注或者收藏哦~

AQS底层原理、运用和volatile详解

1.AbstractQueuedSynchronizer

1.1什么是AQS,为何学习它?

public abstract class AbstractOwnableSynchronizer
    implements java.io.Serializable {

在这里插入图片描述

(1)它是JDK并发编程的基础构建。

(2)AQS是用来构建同步组件的

(3)AbstractQueuedSynchronizer

实现并发编程的时候,都要依赖state状态作文章,就是要去修改其所处状态。

private volatile int state;
1.1.1AQS使用方式和其中的设计模式

(1)AQS在使用方式上面采用的是继承的方式

(2)是在同步工具类的内部,通过定义内部类继承AQS(AbstractQueuedSynchronizer),这个同步工具类对外所暴露的方法,全部将内部类相关的一些操作都封装起来了。使我们觉得没有任何与AQS相关的东西。

(3)如果要自定义一个AQS同步工具类,则需要了解其采用的设计模式。
通常采用的是模板方法的设计模式。

1.1.1.1模版方法设计模式

(1)声明执行一件事情的模版,先做什么,后做什么,最后做什么,即规定了必须要做哪些事情,但不规定具体怎么做这些事情。

/**
 * 类说明:抽象蛋糕模型
 */
public abstract class AbstractCake {
    protected abstract void shape();/*造型*/
    protected abstract void apply();/*涂抹*/
    protected abstract void brake();/*烤面包*/

    /*做个蛋糕 */
    public final void run(){
        this.shape();
        this.apply();
        this.brake();
    }
}

(2)具体实现

/**
 * 类说明:芝士蛋糕
 */
public class CheeseCake  extends AbstractCake {

    @Override
    protected void shape() {
        System.out.println("芝士蛋糕造型");
    }

    @Override
    protected void apply() {
        System.out.println("芝士蛋糕涂抹 芝士");
    }

    @Override
    protected void brake() {
        System.out.println("芝士蛋糕烘焙  5  min");
    }
}
/**
 * 类说明:奶油蛋糕
 */
public class CreamCake extends AbstractCake {
    @Override
    protected void shape() {
        System.out.println("奶油蛋糕造型");
    }

    @Override
    protected void apply() {
        System.out.println("奶油蛋糕涂抹");
    }

    @Override
    protected void brake() {
        System.out.println("奶油蛋糕烘焙");
    }
}

public class MouseCake extends AbstractCake {

    @Override
    protected void shape() {
        System.out.println("慕斯蛋糕造型");
    }

    @Override
    protected void apply() {
        System.out.println("慕斯蛋糕涂抹");
    }

    @Override
    protected void brake() {
        System.out.println("慕斯蛋糕烘焙");
    }
}

(3)生产蛋糕

/**
 * 类说明:生产蛋糕
 */
public class MakeCake {
    public static void main(String[] args) {
        AbstractCake cake = new CheeseCake();
        AbstractCake cake2 = new CreamCake();
		//AbstractCake cake = new MouseCake();
        cake2.run();
    }
}
1.1.1.2Android模版方法设计模式的应用

(1)Android的View绘制规定了子类必须实现onDraw()与dispatchDraw()方法,具体的实现细节在子类中去完成。

1.1.1.3自己需要实现AQS同步工具类

(1)也可以采用模版方法设计模式
(2)可以实现其中的几个方法

1.1.2了解其中的方法
1.1.3实现一个类似于ReentrantLock的锁

(1)任何一个线程进来,都要先拿到一把锁,拿到一把锁以后,才能执行里面相关的业务代码,这就是常用的独占锁。

(2)要标记某个线程已经拿到了锁,改为1.

(3)lock与unlock()一定要实现

(4)lock与unlock()的实现,就需要借助于AQS,它是一个模版方法模式,而且在同步工具类的内部类中需要继承AbstractQueuedSynchronizer。

1.1.3.1案例
/**
 *类说明:实现我们自己独占锁,不可重入
 */
public class SelfLock implements Lock {
    // 静态内部类,自定义同步器
    private static class Sync extends AbstractQueuedSynchronizer {

        /*判断处于占用状态*/
        @Override
        protected boolean isHeldExclusively() {
            return getState()==1;
        }

        /*获得锁*/
        @Override
        protected boolean tryAcquire(int arg) {
            /*
            1.尝试使用compareAndSetState()期望当前这个锁没有人拿,于是将state状态由0改为1
            表示我要去拿这个锁。
            2.compareAndSetState方法返回true,表示锁已经拿到
            3.将当前线程记录一下,告诉别人现在这把锁我独占了,你们都不要用。
            4.compareAndSetState方法返回false,表示锁已经被其他人拿到了,则获得锁失败
             */
            if(compareAndSetState(0,1)){
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        /*释放锁*/
        @Override
        protected boolean tryRelease(int arg) {
            if(getState()==0){
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            /*
            1.如果当前线程已经拿到锁,则将线程的state改为0表示释放该锁。
             */
            setState(0);
            return true;
        }

        /* 返回一个Condition,每个condition都包含了一个condition队列*/
        Condition newCondition() {
            return new ConditionObject();
        }
    }

    /* 仅需要将操作代理到Sync上即可*/
    private final Sync sync = new Sync();

    public void lock() {
    	System.out.println(Thread.currentThread().getName()+" ready get lock");
        sync.acquire(1);
        System.out.println(Thread.currentThread().getName()+" already got lock");
    }

    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    public void unlock() {
    	System.out.println(Thread.currentThread().getName()+" ready release lock");
        sync.release(1);
        System.out.println(Thread.currentThread().getName()+" already released lock");
    }

    public Condition newCondition() {
        return sync.newCondition();
    }

    public boolean isLocked() {
        return sync.isHeldExclusively();
    }

    public boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }

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

    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
}

2.AQS的基本思想CLH队列锁

在这里插入图片描述

(1)任一时刻只有一个线程可以拿到这把锁,其他的线程是要在外面进行排队的。
(2)一个线程把任务完成了,把锁释放了,唤醒另外的线程竞争锁,拿到锁之后,又继续执行。凡是没有拿到锁的线程,都要进行排队。
(3)要排队的线程需要打包成一个QNode,包含三个成员

  • 当前线程本身

  • myPred :当前线程的前驱节点。
    因为有很多个线程都处于等待拿锁阶段,于是就将这些需要拿锁的线程排好队,形成一个链表,链表要么是单向的,要么是双向的。

  • locked:表示标记当前需要获得锁

(4)线程B获取锁的时候,会通过自旋检查链表中其他节点是否释放锁.如果locked字段变成false的时候,表示已经释放了,就可以去拿锁了。

2.1了解ReentrantLock的实现

2.1.1锁的可重入

(1)定义多个同步方法

synchronized a(){
	b();
};

synchronized b()

(2)a方法中调用b方法,这种情况是进a方法的时候需要拿一次锁,进入b方法的时候又要拿一次锁,如果说没有实现锁的可重入,就会导致死锁。即自己将自己给锁住了。

(3)改一下tryAcquire方法就可以了,即在获取锁的时候,多做一次判定,判断一下当前拿到锁的是不是自己,是自己则把state做+1操作,因为要释放锁,所以要加1.

(4)每释放一次锁,将持有锁的次数减1,当减到0的时候,表示对锁的持有已经彻底释放了,通过这种方式实现锁的可重入。

2.1.1.1实现可重入锁
/**
 *类说明:实现我们自己独占锁,可重入
 */
public class ReenterSelfLock implements Lock {
    /* 静态内部类,自定义同步器*/
    private static class Sync extends AbstractQueuedSynchronizer {

        /* 是否处于占用状态*/
        protected boolean isHeldExclusively() {
            return getState() > 0;
        }

        /* 当状态为0的时候获取锁*/
        public boolean tryAcquire(int acquires) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }else if(getExclusiveOwnerThread()==Thread.currentThread()){
                setState(getState()+1);
                return  true;
            }
            return false;
        }

        /* 释放锁,将状态设置为0*/
        protected boolean tryRelease(int releases) {
            if(getExclusiveOwnerThread()!=Thread.currentThread()){
                throw new IllegalMonitorStateException();
            }
            if (getState() == 0)
                throw new IllegalMonitorStateException();

            setState(getState()-1);
            if(getState()==0){
                setExclusiveOwnerThread(null);
            }
            return true;
        }

        /* 返回一个Condition,每个condition都包含了一个condition队列*/
        Condition newCondition() {
            return new ConditionObject();
        }
    }

    /* 仅需要将操作代理到Sync上即可*/
    private final Sync sync = new Sync();

    public void lock() {
    	System.out.println(Thread.currentThread().getName()+" ready get lock");
        sync.acquire(1);
        System.out.println(Thread.currentThread().getName()+" already got lock");
    }

    public boolean tryLock() {
        return sync.tryAcquire(1);
    }

    public void unlock() {
    	System.out.println(Thread.currentThread().getName()+" ready release lock");
        sync.release(1);
        System.out.println(Thread.currentThread().getName()+" already released lock");
    }

    public Condition newCondition() {
        return sync.newCondition();
    }

    public boolean isLocked() {
        return sync.isHeldExclusively();
    }

    public boolean hasQueuedThreads() {
        return sync.hasQueuedThreads();
    }

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

    public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
        return sync.tryAcquireNanos(1, unit.toNanos(timeout));
    }
}
2.1.1.2测试可重入锁
public class TestReenterSelfLock {

    static final Lock lock = new ReenterSelfLock();

    public void reenter(int x){
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName()+":递归层级:"+x);
            int y = x - 1;
            if (y==0) return;
            else{
                reenter(y);
            }
        } finally {
            lock.unlock();
        }

    }

    public void test() {
        class Worker extends Thread {
			public void run() {
                System.out.println(Thread.currentThread().getName());
                SleepTools.second(1);
                reenter(3);
            }
        }
        // 启动3个子线程
        for (int i = 0; i < 3; i++) {
            Worker w = new Worker();
            w.start();
        }
        // 主线程每隔1秒换行
        for (int i = 0; i < 100; i++) {
        	SleepTools.second(1);
        }
    }

    public static void main(String[] args) {
        TestReenterSelfLock testMyLock = new TestReenterSelfLock();
        testMyLock.test();
    }
}
2.1.2公平和非公平锁

(1)公平锁

  • 任何一个线程想要获取锁,必须将其放到链表的最后一个位置,谁都没有例外。

(2)非公平锁

  • 链表上面已经有好几个线程挂到这里,等待着拿锁,外面又有一个线程进来准备拿锁,但是它可以去抢占锁,把锁拿到了,这就称为非公平锁。

2.2将类似于ReentrantLock的锁的实现修正为可重入

(1)可重入锁

3.JMM基础-计算机原理

(1)JMM:Java Memory Model;Java 内存模型。

在这里插入图片描述

(2)高速缓存
为了提高应用程序的性能,会在内存与CPU之间加入高速缓存

在这里插入图片描述

3.1Java内存模型

在这里插入图片描述

3.1.1工作内存

(1)每一个变量的操作只允许在自己的工作内存之中进行操作,而不是操作主内存中的变量。
(2)Java中每个线程的工作内存是自己所独享的,即只准我用,你们都别用。

3.1.2主内存

3.2JMM导致的并发安全问题

在这里插入图片描述

(1)两个线程都要执行count=count+1的操作
(2)刚开始count放在主内存
(3)线程A与线程B都不允许直接对主内存中的count进行直接操作。于是将count读到自己的工作内存中。
(4)于是在线程A的工作内存中就有一个count变量为0;线程B同样如此。
(5)线程A与线程B执行count+1操作,线程A与线程B的工作内存内部,count都变成了1。
(6)线程A与线程B执行完成之后,需要将运算结果写入到主内存中,线程A与线程B都写入1.
(7)理论上来说线程A与线程B都对count进行了一次累加,累加之和应该是2,但是结果却为1.
(8)因此在JMM机制下面,count值为1.就产生了并发编程中的数据不安全问题。

3.2.1变量的可见性问题

(1)线程A将count由0变为1,线程B也将count由0变为1,即线程A与线程B互不知道对方对count进行了修改,所以线程A与线程B之间就存在一个可见性问题。

(2)可见性问题如何解决?

  • 需要在声明变量的时候,使用volatile关键字,即强迫从主内存中去读取数据。
  • 将变量修改之后,强迫将数据刷新到主内存。
  • 使用了volatile关键字结果还是不正确,是因为volatile只是强迫读了,算完了强制放回主内存。
  • 如果进行多次运算,还是只用volatile进行一个修饰,线程A与线程B,线程A先做,将count值由0变为了1,同步回主内存。线程B将1读到工作内存中,这个count=count+1不是一个原子操作,线程B在累加的过程中,因为它不是一个原子操作,意味着B这个累加过程是可以被打断的,比如说上下文切换,即只算到某一条指令,算到一半的时候,操作系统把B给交换出去了,B不执行了,A没有交换出去,一直做累加操作。A下一次运算,将count变为了2,又同步回去,B又重新切换回来,继续count=count+1,这个时候线程A已经对count进行了修改,线程B上次的count操作还没有完成,它是没有到主内存中去读一次值的过程,它只有下一次运算的时候才会去主内存中去读,所以这时候,即使加了volatile关键字,结果也是错的。因此操作需要保证原子性。
  • 加volatile关键字只能保证可见性,不能保证操作的原子性。
3.2.2变量的原子性问题

(1)加锁可以保证运算的原子性。
(2)因为即使运算过程中上下文切换时交换出去了,因为其他线程拿不到锁,就无法对变量进行修改,一定是等这个有锁的线程处理完了,其他线程才能拿到锁,才能对其进行修改。
(3)只加锁,不使用volatile关键字修饰变量也是可以的,因为synchronized关键字的强度要比volatile的强度更强,即volatile是JDK中提供的最轻量级的同步机制。
(4)synchronized同时保证了可见性与原子性。

4.volatile详解

在这里插入图片描述

4.1volatile关键字的应用场景

(1)如果只有一个线程写,多个线程读的时候,volatile是完全没有问题的,能够确保线程安全。
(2)写操作之间没有任何的关联,即count值赋预的是完全独立的值
count=5;
count=6;
而不是关联
count=count+1;

(3)其在JDK编程里面大量使用,即实现无锁化编程。

4.2volatile的实现原理

(1)有Volatile变量修饰的共享变量进行写操作的时候会使用CPU提供的Lock前缀指令

  • 将当前处理器缓存行的数据写回到系统内存。
  • 这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。

5.打赏鼓励

感谢您的细心阅读,您的鼓励是我写作的不竭动力!!!

5.1微信打赏

在这里插入图片描述

5.2支付宝打赏

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值