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方法中创建了多个线程并发执行这两个操作,并通过输出来观察读写锁的效果。

四、结尾

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

相关推荐
魔道不误砍柴功2 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2342 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨2 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
测开小菜鸟4 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity4 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天5 小时前
java的threadlocal为何内存泄漏
java
caridle5 小时前
教程:使用 InterBase Express 访问数据库(五):TIBTransaction
java·数据库·express
^velpro^5 小时前
数据库连接池的创建
java·开发语言·数据库
苹果醋35 小时前
Java8->Java19的初步探索
java·运维·spring boot·mysql·nginx
秋の花5 小时前
【JAVA基础】Java集合基础
java·开发语言·windows