AQS

AQS

  • 多线程存在数据不一致性
  • volatile
  • synchronized
  • JUC包

  • 锁分类
    • 悲观锁:用于 写多,读少,
    • 乐观锁:用于 读多,写少,<版本控制方式>

CAS

  • 概念
    • CompareAndSet
    • CompareAndSwap:比较交换,一种无锁的原子算法,属于乐观锁
  • 基本思想
    • 给出一个期望值,与现有值比较,如果相等进行修改,如果不相等,那么就无法修改
    • 表示方式:CAS (V, E, N),如果 V==E(期望值),将 N 赋值给 V
  • 作用和优点:
    • 实现复杂,但无锁(因此不存在阻塞),效率高<CPU吞吐量高>
ABA问题

出现

  • t1修改i(100-> 110)
  • t2修改(110-> 100)
  • t3修改(100-> 120),此时t3认为它拿到的100还是初始的100,出现ABA
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class CASDemo {

    private static AtomicInteger atomicInteger =  new AtomicInteger(0);

    public static void main(String[] args) {
        new Thread(() -> {
            atomicInteger.compareAndSet(100, 110);
        }).start();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                atomicInteger.compareAndSet(110, 100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                atomicInteger.compareAndSet(100, 120);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

在这里插入图片描述
解决
- 使用 AtomicStampedReference
- 相当于加了版本<时间戳>
- 思路:原来是 A-B-A 加版本后:A1-B2-A3

public class CASDemo2 {
    /**
     * 给出值后,给出版本
     */
    private static AtomicStampedReference asr =
            new AtomicStampedReference(100, 1);

    public static void main(String[] args) {
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
                System.out.println(asr.compareAndSet(100, 110, asr.getStamp(), asr.getStamp()+1));
                System.out.println(asr.compareAndSet(110, 100, asr.getStamp(), asr.getStamp()+1));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();

        /**
         * 先拿到版本,停留一段时间等待其他线程修改
         * 此时该线程执行时,版本不对,修改失败
         */
        new Thread(() -> {
            try {
                int stamp = asr.getStamp();
                TimeUnit.SECONDS.sleep(3);
                System.out.println(asr.compareAndSet(110, 100, stamp, stamp+1));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

CAS 应用场景

(1) 适用于简单的数据计算
(2) 适合线程冲突少的场景

AQS

  • 概念
  • * AbstractQueuedSychronizer 同步发生器,构造 JUC、Lock等
    
  • 基本思想
  •  * 通过内置得到FIFO同步队列来完成线程争夺资源的管理工作
    
  • CLH同步队列
自定义锁
package test.aqsImpl;

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 Helper helper = new Helper();

    private class Helper extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            int state = getState();
            if(state == 0) {
                //使用 CAS 修改 state
                if (compareAndSetState(0, arg)) {
                    //修改成功,设置当前线程占有资源
                    setExclusiveOwnerThread(Thread.currentThread());
                    return true;
                }
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            int state = getState() - arg;
            boolean flag = false;
            if(state == 0) {
                //如果 state = 0  说明其他线程可以占有该资源
                setExclusiveOwnerThread(null);
                return true;
            }
            // 这里不存在线程安全问题,因为释放前已经占有了资源
            setState(state);
            return false;
        }

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

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

    /**
     * 以中断方式获取锁
     * @throws InterruptedException
     */
    @Override
    public void lockInterruptibly() throws InterruptedException {
        helper.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return helper.tryAcquire(1);
    }

    /**
     * 多久没有获取锁就抛出异常
     * @param time
     * @param unit
     * @return
     * @throws InterruptedException
     */
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return helper.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override
    public void unlock() {
        helper.release(1);
    }

    /**
     * 在某些条件下 进行加锁
     * @return
     */
    @Override
    public Condition newCondition() {
        return helper.newConditionObject();
    }
}

测试,解决数据不一致问题,输出顺序可能时乱序,但是保证了数据时一致的,也就是不会出现重复值

package test.aqsImpl;

public class TestMyLock {
    private int m = 0;
    private MyLock myLock = new MyLock();
    public int next() {
        myLock.lock();
        try {
            //TimeUnit.SECONDS.sleep(2);
            return m++;
        } finally {
            myLock.unlock();
        }
    }

    public static void main(String[] args) {
        TestMyLock testMyLock = new TestMyLock();
        for (int i = 0; i < 20; i++) {
            new Thread(()->{
                System.out.println(testMyLock.next());
            }).start();
        }
    }
}

修改使其具有可重入性

package test.aqsImpl;

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 Helper helper = new Helper();

    private class Helper extends AbstractQueuedSynchronizer {
        @Override
        protected boolean tryAcquire(int arg) {
            int state = getState();
            if(state == 0) {
                //使用 CAS 修改 state
                if (compareAndSetState(0, arg)) {
                    //修改成功,设置当前线程占有资源
                    setExclusiveOwnerThread(Thread.currentThread());
                    return true;
                }
                //修改具有可重入性
            } else if(Thread.currentThread() == getExclusiveOwnerThread()) {
                setState(getState() + arg);
                return true;
            }
            return false;
        }

        @Override
        protected boolean tryRelease(int arg) {
            int state = getState() - arg;
            boolean flag = false;
            if(state == 0) {
                //如果 state = 0  说明其他线程可以占有该资源
                setExclusiveOwnerThread(null);
                return true;
            }
            // 这里不存在线程安全问题,因为释放前已经占有了资源
            setState(state);
            return false;
        }

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

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

    /**
     * 以中断方式获取锁
     * @throws InterruptedException
     */
    @Override
    public void lockInterruptibly() throws InterruptedException {
        helper.acquireInterruptibly(1);
    }

    @Override
    public boolean tryLock() {
        return helper.tryAcquire(1);
    }

    /**
     * 多久没有获取锁就抛出异常
     * @param time
     * @param unit
     * @return
     * @throws InterruptedException
     */
    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return helper.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override
    public void unlock() {
        helper.release(1);
    }

    /**
     * 在某些条件下 进行加锁
     * @return
     */
    @Override
    public Condition newCondition() {
        return helper.newConditionObject();
    }
}
Lock的实现类
ReentrantLock
 * 公平锁
 * 非公平锁
  • ReentrantReadWriteLock implement ReadWriteLock
    • 读写锁
CountDownLatch
  • 类似于计数器,比如有个任务A,必须等待其他4个任务完成才能执行
  • 每当一个任务完成后,计数器减一,当计数器为0,那么可以恢复执行任务
package test.aqsImpl;

import java.util.concurrent.CountDownLatch;

public class CountDownLatchTest {

    public static void main(String[] args) {
        String[] works = new String[]{"work1", "work2", "work3", "work4"};
        CountDownLatch countDownLatch = new CountDownLatch(works.length);

        for (int i = 0; i < works.length; i++) {
            int finalI = i;
            new Thread(()->{
                try {
                    System.out.printf("Doing %s\n", works[finalI]);
                    //完成任务  信号量减一
                    countDownLatch.countDown();
                    Thread.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }).start();
        }
        try {
            //完成所有任务后唤醒
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}


CyclicBarrier
  • 原理:每个线程执行时都会碰到一个屏障,直到所有线程准备完成,屏障才会打开,使所有线程继续往下执行
  • 首先传入一个 初始值 pairs,当每个线程准备完成后,state + 1,与初始值比较,当state = pairs,所有线程准备完成,可以继续执行自己剩余的任务
package test.aqsImpl;

import java.util.Random;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.TimeUnit;

public class CyclicBarrierTest {

    public static void main(String[] args) {
        CyclicBarrier cyclicBarrier = new CyclicBarrier(8);
        for (int i = 0; i < cyclicBarrier.getParties(); i++) {
            new Thread(()->{
                try {
                    TimeUnit.SECONDS.sleep(new Random().nextInt(2));
                    System.out.println(Thread.currentThread().getName() + "准备好了");
                    //准备工作完成后 唤醒状态
                    cyclicBarrier.await();
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "开始执行");
            }).start();

        }
    }
}

效果
在这里插入图片描述

Semaphore 信号量
  • 信号量是可以使用的资源数量
  1. 请求资源 aquire()
  2. 使用资源
  3. 释放资源 release()
  • 模拟停车场
package aqsImpl.semaphore;

import java.util.Random;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * 模拟停车场
 * 现有2个车位,5辆车待停
 * 
 * 如果车位占满,那么其他车就无法停入,除非在停车离开
 */
public class ParkStation {

    public static void main(String[] args) {
        //创建信号量
        Semaphore semaphore = new Semaphore(2, true);
        System.out.println("parking open ---");
        for (int i = 0; i < 5; i++) {
            new Thread(()->{
                try {
                    semaphore.acquire();

                } catch (Exception e) {
                    e.printStackTrace();
                }
                try {
                    System.out.println(Thread.currentThread() + " parked");
                    TimeUnit.SECONDS.sleep(new Random().nextInt(5));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                semaphore.release();
                System.out.println(Thread.currentThread() + " leaved");
            }).start();
        }
    }
}

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值