JAVA每日面经——并发编程(一)必看

👩🏽‍💻个人主页:阿木木AEcru

🔥 系列专栏:《Docker容器化部署系列》 《Java每日面筋》

💹每一次技术突破,都是对自我能力的挑战和超越。

一、并发编程之AQS

AQS(AbstractQueuedSynchronizer)是 Java 并发编程中的一个重要组件,作为多线程同步器,它在 Java 并发包(java.util.concurrent)多个组件的底层实现。如 Lock、CountDownLatch、Semaphore 等都用到了它。

在 AQS 的设计中,它主要提供了两种锁机制:排它锁和共享锁。

  • 排它锁(Exclusive Lock):也称为独占锁,它确保在同一时刻只有一个线程可以访问共享资源。这意味着当多个线程竞争同一资源时,只有一个线程能够获得锁,并且其他线程必须等待。比如,ReentrantLock 中的重入锁就是排它锁的一种实现,它利用 AQS 实现了这种独占的锁机制。
  • 共享锁(Shared Lock):也称为读锁,它允许多个线程同时访问共享资源,从而提高了并发性。多个线程可以同时获得锁资源,这在某些场景下是非常有用的。比如,CountDownLatch 和 Semaphore 就是利用 AQS 中的共享锁实现的。

它主要由以下几个组成部分构成:

  1. 节点(Node): 用于封装等待获取同步状态的线程。每个节点都包含了线程的引用和状态信息,以双向链表的形式组织在一起,构成等待队列。
  2. 状态(State): 表示共享资源的状态,可以是任意的整数值,代表不同的含义,比如锁的状态或信号量的剩余资源数量。
  3. 等待队列(Wait Queue): 存放等待获取同步状态的线程节点的双向链表。
  4. 同步器方法(Sync Methods): 定义了 AQS 的抽象方法,子类通过实现这些方法来控制同步状态的获取和释放行为,比如tryAcquire()、tryRelease()等。
  5. 条件对象(Condition): 支持精确线程等待和通知机制,用于在满足特定条件之前等待,并在条件满足时被唤醒。

下面的cas它也有用到,继续往下看吧。

二、并发编程之CAS

CAS(Compare and Swap)它的全称是 CompareAndSwap,比较并交换的意思。它的主要功能是能够保证在多线程环境下,对于共享变量的修改的原子性。CAS 主要用在并发场景中,比较典型的使用场景有两个。

  • 第一个是 J.U.C 里面 Atomic 的原子实现,比如 AtomicInteger,AtomicLong。
  • 第二个是实现多线程对共享资源竞争的互斥性质,比如在 AQS、 ConcurrentHashMap、ConcurrentLinkedQueue 等都有用到。

而CAS操作包含三个操作数:内存地址(或变量)、期望值和新值。其主要逻辑如下:

  1. 比较内存地址处的值和期望值是否相等。
  2. 如果相等,则将内存地址处的值更新为新值。
  3. 如果不相等,则不做任何操作。

CAS通常用于实现一些基本的同步原语,如自旋锁、乐观锁等。相比synchronized来说,CAS具有更低的开销,因为它避免了线程的阻塞和唤醒操作,而是通过轮询的方式来实现同步。

而平时在做一些业务时,也是可以参考这一块逻辑作为一个乐观锁的实现。例如最简单的就是在mysql中加一个版本号 version,在更新这一条数据之前,先查询这条旧数据,获取到当前版本号,再更新的时候,将旧的版本号作为条件( where version = 获取到的版本号 ),更新的时候加上(set version = version+1) , 只要修改的行数大于1则证明修改成功,如果修改失败的话,可以进行一个轮询重试,间隔由你自己把控,直到更新成功为止。

ini 复制代码
UPDATE <表名> SET column1 = 'new_value', version = version + 1 WHERE id = <通过id修改> AND version = <旧版本号>;

三、java中常用的锁有哪些?

常用的锁机制包括Synchronized锁、ReentrantLock锁和ReadWriteLock读写锁。下面分别介绍这三种锁。

1. Synchronized锁

Synchronized是Java中最基本的锁机制之一,通过对代码块或方法添加synchronized关键字来实现同步。Synchronized锁是一种悲观锁,它会在进入代码块之前获取锁,并在退出代码块时释放锁。

示例代码:

java 复制代码
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * SynchronizedExample示例演示了使用Synchronized关键字实现对共享资源的并发访问控制。
 */
public class SynchronizedExample {
    private int count = 0; // 共享资源计数器

    /**
     * 同步方法increment,通过synchronized关键字实现对共享资源的加锁。
     * 每次调用该方法时,只允许一个线程进入,确保对count变量的操作是原子性的。
     */
    public synchronized void increment() {
        count++; // 增加计数器的值
    }

    /**
     * 同步代码块incrementSyncBlock,通过对this对象进行加锁实现对共享资源的保护。
     * 与同步方法相比,同步代码块可以更灵活地控制锁的范围,避免不必要的锁竞争。
     */
    public void incrementSyncBlock() {
        synchronized (this) {
            count++; // 增加计数器的值
        }
    }

    /**
     * 获取计数器的当前值。
     *
     * @return 计数器的当前值
     */
    public int getCount() {
        return count; // 返回计数器的值
    }

    public static void main(String[] args) {
        SynchronizedExample example = new SynchronizedExample();
        ExecutorService executor = Executors.newCachedThreadPool();

        // 创建多个线程并发执行increment操作
        for (int i = 0; i < 5; i++) {
            executor.execute(() -> {
                for (int j = 0; j < 100; j++) {
                    example.increment(); // 调用increment方法增加计数器的值
                }
            });
        }

        // 关闭线程池
        executor.shutdown();

        // 等待所有线程执行完毕
        while (!executor.isTerminated()) {
        }

        // 输出最终的计数器值
        System.out.println("结果: " + example.getCount());
    }
}

创建了一个线程池ExecutorService来执行多个线程并发调用increment方法,每个线程循环执行100次。通过ExecutorService的shutdown方法关闭线程池,并通过isTerminated方法等待所有线程执行完毕。

2. ReentrantLock锁

ReentrantLock是Java.util.concurrent包中提供的锁机制,它提供了与Synchronized锁类似的功能,但具有更灵活的锁获取和释放方式。ReentrantLock锁是一种可重入锁,允许同一个线程多次获取同一把锁。

示例代码:

csharp 复制代码
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class LockExample {
    private int count = 0;
    private final Lock lock = new ReentrantLock(); // 创建一个可重入锁实例

    public void increment() {
        lock.lock(); // 获取锁
        try {
            count++; // 增加计数器的值
        } finally {
            lock.unlock(); // 释放锁
        }
    }

    public int getCount() {
        lock.lock(); // 获取锁
        try {
            return count; // 返回计数器的值
        } finally {
            lock.unlock(); // 释放锁
        }
    }

    public static void main(String[] args) {
        LockExample example = new LockExample();

        // 创建多个线程并发执行increment操作
        for (int i = 0; i < 5; i++) {
            Thread thread = new Thread(() -> {
                for (int j = 0; j < 100; j++) {
                    example.increment(); // 调用increment方法增加计数器的值
                }
            });
            thread.start(); // 启动线程
        }

        // 等待所有线程执行完毕
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 输出最终的计数器值
        System.out.println("结果: " + example.getCount());
    }
}

通过使用ReentrantLock实现了对计数器的并发访问控制。在increment和getCount方法中,通过lock和unlock方法实现了对共享资源的加锁和解锁操作,确保了线程安全性。同时,在main方法中创建了多个线程并发执行increment操作,通过输出最终的计数器值验证了线程安全性。

3. ReadWriteLock读写锁

ReadWriteLock是一种特殊的锁机制,它将锁分为读锁和写锁两种类型,允许多个线程同时获取读锁,但只允许一个线程获取写锁。读写锁适用于读操作频繁、写操作较少的场景,可以提高并发读取的效率。

示例代码:

java 复制代码
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockExample {
    private int count = 0;
    private final ReadWriteLock rwLock = new ReentrantReadWriteLock();

    public void increment() {
        rwLock.writeLock().lock(); // 获取写锁
        try {
            count++; // 增加计数器的值
            System.out.println("递增至: " + count);
        } finally {
            rwLock.writeLock().unlock(); // 释放写锁
        }
    }

    public int getCount() {
        rwLock.readLock().lock(); // 获取读锁
        try {
            System.out.println("当前值: " + count);
            return count; // 返回计数器的值
        } finally {
            rwLock.readLock().unlock(); // 释放读锁
        }
    }

    public static void main(String[] args) {
        ReadWriteLockExample example = new ReadWriteLockExample();

        // 创建多个线程并发执行increment操作
        for (int i = 0; i < 3; i++) {
            new Thread(() -> {
                example.increment(); // 调用increment方法增加计数器的值
            }).start();
        }

        // 创建多个线程并发执行getCount操作
        for (int i = 0; i < 5; i++) {
            new Thread(() -> {
                example.getCount(); // 调用getCount方法获取计数器的值
            }).start();
        }
    }
}

在这里创建了一个ReadWriteLockExample类,其中包含increment和getCount两个方法,分别用于增加计数器的值和获取计数器的值。在main方法中创建了多个线程并发执行这两个操作,并通过输出来观察读写锁的效果。

四、结尾

感谢您的观看! 如果本文对您有帮助,麻烦用您发财的小手点个三连吧!您的支持就是作者前进的最大动力!再次感谢!

相关推荐
挺菜的15 分钟前
【算法刷题记录(简单题)003】统计大写字母个数(java代码实现)
java·数据结构·算法
掘金-我是哪吒1 小时前
分布式微服务系统架构第156集:JavaPlus技术文档平台日更-Java线程池使用指南
java·分布式·微服务·云原生·架构
亲爱的非洲野猪1 小时前
Kafka消息积压的多维度解决方案:超越简单扩容的完整策略
java·分布式·中间件·kafka
wfsm1 小时前
spring事件使用
java·后端·spring
小飞悟2 小时前
你以为 React 的事件很简单?错了,它暗藏玄机!
前端·javascript·面试
微风粼粼2 小时前
程序员在线接单
java·jvm·后端·python·eclipse·tomcat·dubbo
缘来是庄2 小时前
设计模式之中介者模式
java·设计模式·中介者模式
rebel2 小时前
若依框架整合 CXF 实现 WebService 改造流程(后端)
java·后端
掘金安东尼3 小时前
技术解析:高级 Excel 财务报表解析器的架构与实现
前端·javascript·面试
天天扭码3 小时前
AI时代,前端如何处理大模型返回的多模态数据?
前端·人工智能·面试