👩🏽💻个人主页:阿木木AEcru
🔥 系列专栏:《Docker容器化部署系列》 《Java每日面筋》
💹每一次技术突破,都是对自我能力的挑战和超越。
一、并发编程之AQS
AQS(AbstractQueuedSynchronizer)是 Java 并发编程中的一个重要组件,作为多线程同步器,它在 Java 并发包(java.util.concurrent)多个组件的底层实现。如 Lock、CountDownLatch、Semaphore 等都用到了它。
在 AQS 的设计中,它主要提供了两种锁机制:排它锁和共享锁。
- 排它锁(Exclusive Lock):也称为独占锁,它确保在同一时刻只有一个线程可以访问共享资源。这意味着当多个线程竞争同一资源时,只有一个线程能够获得锁,并且其他线程必须等待。比如,ReentrantLock 中的重入锁就是排它锁的一种实现,它利用 AQS 实现了这种独占的锁机制。
- 共享锁(Shared Lock):也称为读锁,它允许多个线程同时访问共享资源,从而提高了并发性。多个线程可以同时获得锁资源,这在某些场景下是非常有用的。比如,CountDownLatch 和 Semaphore 就是利用 AQS 中的共享锁实现的。
它主要由以下几个组成部分构成:
- 节点(Node): 用于封装等待获取同步状态的线程。每个节点都包含了线程的引用和状态信息,以双向链表的形式组织在一起,构成等待队列。
- 状态(State): 表示共享资源的状态,可以是任意的整数值,代表不同的含义,比如锁的状态或信号量的剩余资源数量。
- 等待队列(Wait Queue): 存放等待获取同步状态的线程节点的双向链表。
- 同步器方法(Sync Methods): 定义了 AQS 的抽象方法,子类通过实现这些方法来控制同步状态的获取和释放行为,比如tryAcquire()、tryRelease()等。
- 条件对象(Condition): 支持精确线程等待和通知机制,用于在满足特定条件之前等待,并在条件满足时被唤醒。
下面的cas它也有用到,继续往下看吧。
二、并发编程之CAS
CAS(Compare and Swap)它的全称是 CompareAndSwap,比较并交换的意思。它的主要功能是能够保证在多线程环境下,对于共享变量的修改的原子性。CAS 主要用在并发场景中,比较典型的使用场景有两个。
- 第一个是 J.U.C 里面 Atomic 的原子实现,比如 AtomicInteger,AtomicLong。
- 第二个是实现多线程对共享资源竞争的互斥性质,比如在 AQS、 ConcurrentHashMap、ConcurrentLinkedQueue 等都有用到。
而CAS操作包含三个操作数:内存地址(或变量)、期望值和新值。其主要逻辑如下:
- 比较内存地址处的值和期望值是否相等。
- 如果相等,则将内存地址处的值更新为新值。
- 如果不相等,则不做任何操作。
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方法中创建了多个线程并发执行这两个操作,并通过输出来观察读写锁的效果。
四、结尾
感谢您的观看! 如果本文对您有帮助,麻烦用您发财的小手点个三连吧!您的支持就是作者前进的最大动力!再次感谢!