攻克并发编程与高并发的难题视频四

一、AbstractQueuedSynchronizer(AQS)介绍

AQS底层使用的是双向列表(Sync queue)
Condition queue使用的是单向列表。

在AQS中有一个int类型表示状态,states为0表示没有线程获得锁,1表示有现场获得锁,大于1表示重入锁的数量。

在这里插入图片描述
在这里插入图片描述

二、AQS同步组建(基于AQS)

在这里插入图片描述
在这里插入图片描述

1、CountDownLatch

在这里插入图片描述
一个线程或者多个线程一直等待,直到其他线程执行的操作完成。

CountDownLatch给定一个参数进行初始化,该计数器的操作是一个原子操作,同时只能有一个线程操作该计数器。调用await方法会一直处于阻塞状态,直到其他线程将计数器的值变成0,每次调用countDown() 计数器的值就会减去1。计数器不能重置。

如果业务上需要一个可以重置计数次数的类,可以考虑下面介绍的CyclicBarrier

代码案例

package com.mmall.concurrency.example.aqs;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
public class CountDownLatchExample1 
{

    private final static int threadCount = 200;

    public static void main(String[] args) throws Exception 
    {

        ExecutorService exec = Executors.newCachedThreadPool();

        final CountDownLatch countDownLatch = new CountDownLatch(threadCount);

        for (int i = 0; i < threadCount; i++) 
        {
            final int threadNum = i;
            exec.execute(() -> {
                try 
                {
                    test(threadNum);
                }
                 catch (Exception e) 
                 {
                    log.error("exception", e);
                }
                 finally 
                 {
                    countDownLatch.countDown();
                }
            });
        }
        countDownLatch.await();
        log.info("finish");
        exec.shutdown();
    }

    private static void test(int threadNum) throws Exception {
        Thread.sleep(100);
        log.info("{}", threadNum);
        Thread.sleep(100);
    }
}

任务在指定时间完成,超过时间没做完就不管了 await(num,单位)

package com.mmall.concurrency.example.aqs;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;

@Slf4j
public class CountDownLatchExample2 {

    private final static int threadCount = 200;

    public static void main(String[] args) throws Exception {

        ExecutorService exec = Executors.newCachedThreadPool();

        final CountDownLatch countDownLatch = new CountDownLatch(threadCount);

        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            exec.execute(() -> {
                try {
                    test(threadNum);
                } catch (Exception e) {
                    log.error("exception", e);
                } finally {
                    countDownLatch.countDown();
                }
            });
        }
        countDownLatch.await(10, TimeUnit.MILLISECONDS);
        log.info("finish");
        exec.shutdown();
    }

    private static void test(int threadNum) throws Exception {
        Thread.sleep(100);
        log.info("{}", threadNum);
    }
}

上面的代码先输出finish,再输出其他线程执行的test方法。

线程池调用shutdown方法,不是第一时间把全部线程销毁掉,而是让当前已有的线程全部执行完之后再把线程池销毁掉。

2、Semaphore信号量

在这里插入图片描述
控制某个资源可被同时访问的个数。
acquire获取一个许可,如果没有则等待,release则在操作完成之后放出一个许可。
Semaphore控制当前访问的个数,提供同步机制来控制同时访问的个数。

Semaphore semaphore = new Semaphore(3); 参数是几表示控制同时访问的资源数为几。
tryAcquire方法表示尝试获取许可。

1、acquire()获取一个许可,acquire(n)获取多个许可

package com.mmall.concurrency.example.aqs;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

@Slf4j
public class SemaphoreExample1 {

    private final static int threadCount = 20;

    public static void main(String[] args) throws Exception {

        ExecutorService exec = Executors.newCachedThreadPool();

        final Semaphore semaphore = new Semaphore(3);

        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            exec.execute(() -> {
                try {
                    semaphore.acquire(); // 获取一个许可
                    test(threadNum);
                    semaphore.release(); // 释放一个许可
                } catch (Exception e) {
                    log.error("exception", e);
                }
            });
        }
        exec.shutdown();
    }

    private static void test(int threadNum) throws Exception {
        log.info("{}", threadNum);
        Thread.sleep(1000);
    }
}

2、tryAcquire尝试获取一个许可,也可以一次获得多许可,参数int

3、semaphore.tryAcquire(5000, TimeUnit.MILLISECONDS))一定时间内尝试获取许可

package com.mmall.concurrency.example.aqs;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

@Slf4j
public class SemaphoreExample3 {

    private final static int threadCount = 20;

    public static void main(String[] args) throws Exception {

        ExecutorService exec = Executors.newCachedThreadPool();

        final Semaphore semaphore = new Semaphore(3);

        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            exec.execute(() -> {
                try {
                    if (semaphore.tryAcquire()) { // 尝试获取一个许可
                        test(threadNum);
                        semaphore.release(); // 释放一个许可
                    }
                } catch (Exception e) {
                    log.error("exception", e);
                }
            });
        }
        exec.shutdown();
    }

    private static void test(int threadNum) throws Exception {
        log.info("{}", threadNum);
        Thread.sleep(1000);
    }
}

3、CyclicBarrier

在这里插入图片描述
允许一组线程相互等待,直到到达某个公共屏障点,通过它可以完成多个线程之间相互等待,只有当每个线程都准备就绪后,才能继续往下执行,它和CountDownLatch有些相似的地方,都是通过计数器来实现的,当某个线程调用了await方法之后,线程就进入了等待状态,计数器就执行加一操作,当计数器的值达到了初始值,进入等待的现场就会被唤醒继续执行后续的操作,CyclicBarrier可以重用,所以又称为循环屏障

CyclicBarrier可以用于多线程计算数据,最后合并运算结果的应用场景。

await可以有时间参数。

package com.mmall.concurrency.example.aqs;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

@Slf4j
public class CyclicBarrierExample1 
{

    private static CyclicBarrier barrier = new CyclicBarrier(5); //5表示一共有五个线程一起同步等待。

    public static void main(String[] args) throws Exception 
    {

        ExecutorService executor = Executors.newCachedThreadPool();

        for (int i = 0; i < 10; i++) 
        {
            final int threadNum = i;
            Thread.sleep(1000);
            executor.execute(() -> {
                try 
                {
                    race(threadNum);
                } 
                catch (Exception e) 
                {
                    log.error("exception", e);
                }
            });
        }
        executor.shutdown();
    }

    private static void race(int threadNum) throws Exception 
    {
        Thread.sleep(1000);
        log.info("{} is ready", threadNum);
        barrier.await();
        log.info("{} continue", threadNum);
    }
}

CountDownLatch和CyclicBarrier区别

  • 1、CountDownLatch的计数器只能使用一次,CyclicBarrier的计数器可以使用方法重置循环使用。
  • 2、CountDownLatch主要实现一个或者多个线程需要等待,其他线程完成某个操作之后,这些等待的线程才能继续往下执行。CyclicBarrier主要实现多个线程之间相互等待,直到所有线程都满足条件之后才能继续执行后续的操作。换句话说,CountDownLatch就是等待其他线程完成,我才能继续执行,而CyclicBarrier指的是n个线程之间相互等待

三、ReentrantLock与锁、ReentrantReadWriteLock

1、ReentrantLock(可重入锁)和synchronized区别

在这里插入图片描述

  • 锁是实现方面的区别:Synchronized关键字是依赖JVM实现的,而ReentrantLock是依赖JDK实现的。区别就在于一个是类似于操作系统操作实现,而另外一个是代码实现的区别。
  • 性能的区别:在Synchronized关键字优化之前,Synchronized关键字的性能比ReentrantLock性能要差很多,但是自从synchronized关键字引入了偏向锁、轻量级锁后,两者的性能就差不多了。
  • 功能点区别:synchronized的使用比较方便简洁并且是由编译器保证加锁和释放的,而ReentrantLock要我们手工加锁和释放锁,如果忘记手工释放锁会造成死锁。

2、ReentrantLock独有功能

synchronized只能是非公平锁,而ReentrantLock可以指定公平锁和非公平锁。
公平锁是先等待的线程先获得锁。

在这里插入图片描述ReentrantLock实现是一种自旋锁,通过循环调用CAS操作来实现加锁。

ReentrantLock示例代码:

package com.mmall.concurrency.example.lock;

import com.mmall.concurrency.annoations.ThreadSafe;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
@ThreadSafe
public class LockExample2 {

    // 请求总数
    public static int clientTotal = 5000;

    // 同时并发执行的线程数
    public static int threadTotal = 200;

    public static int count = 0;

    private final static Lock lock = new ReentrantLock();

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newCachedThreadPool();
        final Semaphore semaphore = new Semaphore(threadTotal);
        final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
        for (int i = 0; i < clientTotal ; i++) {
            executorService.execute(() -> {
                try {
                    semaphore.acquire();
                    add();
                    semaphore.release();
                } catch (Exception e) {
                    log.error("exception", e);
                }
                countDownLatch.countDown();
            });
        }
        countDownLatch.await();
        executorService.shutdown();
        log.info("count:{}", count);
    }

    private static void add() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
}

在这里插入图片描述ReentrantLock有很多函数,如上图。
lockInterruptibly:如果当前线程没有被中断的话,那么就获取锁定,如果已经被中断了,那么就抛出异常。

Condition

package com.mmall.concurrency.example.lock;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class LockExample6 {

    public static void main(String[] args) {
        ReentrantLock reentrantLock = new ReentrantLock();
        Condition condition = reentrantLock.newCondition();

        new Thread(() -> {
            try {
                reentrantLock.lock();
                log.info("wait signal"); // 1
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("get signal"); // 4
            reentrantLock.unlock();
        }).start();

        new Thread(() -> {
            reentrantLock.lock();
            log.info("get lock"); // 2
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            condition.signalAll();
            log.info("send signal ~ "); // 3
            reentrantLock.unlock();
        }).start();
    }
}

reentrantLock.lock()之后,线程会被加到AQS的等待队列中,在condition.await()方法之后,线程从AQS队列中移除,对应的操作是锁的释放,然后把线程加入到了Condition的等待队列中,等待的线程需要其他线程给一个信号,如:signalAll。
Condition是多线程之间协调的工具类。

3、ReentrantReadWriteLock

package com.mmall.concurrency.example.lock;

import lombok.extern.slf4j.Slf4j;

import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

@Slf4j
public class LockExample3 {

    private final Map<String, Data> map = new TreeMap<>();

    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    private final Lock readLock = lock.readLock();

    private final Lock writeLock = lock.writeLock();

    public Data get(String key) {
        readLock.lock();
        try {
            return map.get(key);
        } finally {
            readLock.unlock();
        }
    }

    public Set<String> getAllKeys() 
    {
        readLock.lock();
        try 
        {
            return map.keySet();
        } 
        finally 
        {
            readLock.unlock();
        }
    }

    public Data put(String key, Data value) 
    {
        writeLock.lock();
        try 
        {
            return map.put(key, value);
        } 
        finally 
        {
            readLock.unlock();
        }
    }

    class Data {

    }
}

在写入的时候不允许有读锁还保持着,这样就要求写入时候所有做的事情做完了,因此存在一个问题,读取情况很多的时候,写入很少的时候,这个类可能会让线程处于饥饿。

4、StampedLock

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值