四、JUC锁实现类

抽象队列同步器 AQS

概念

1)AQS是AbstractQueuedSynchronizer的简称,即抽象队列同步器

抽象:抽象类,只实现一些主要逻辑,有些方法由子类实现

队列:使用先进先出(FIFO)的队列存储数据

同步:实现了同步的功能

2)AQS 就是起到了一个抽象、封装的作用,运用模板方法模式,将一些排队、入队等通用方法提供出来,具体加锁时机、入队时机等都需要实现类自己控制

3)AQS 常见的实现类有 ReentrantLock、ReentrantReadWriteLock 等

数据结构

维护一个共享状态(state)和一个先进先出的同步队列,队列采用双向链表结构,节点包含线程的引用、等待状态以及前驱和后继节点的指针

state 用 volatile 修饰,表示当前资源的状态

当线程尝试获取资源失败时,会被加入到 AQS 的同步队列中

重入锁 ReentrantLock

概念

1)线程获取锁时,若已获取锁的线程是当前线程,则直接再次获取成功

2)由于锁被获取 n 次,那么只有锁在被释放同样的 n 次之后,该锁才算是完全释放成功

基本使用

创建锁时,构造函数传入true为公平锁,不传为非公平锁

使用ReentrantLock时,锁必须在 try 代码块开始之前获取。必须在 finally 块内释放锁

(若在 try 块内加锁,加锁前发生了异常,在 finally 块内会对未上锁的锁进行释放操作)

public class ReentrantLockTest {
    private static final ReentrantLock lock = new ReentrantLock();//申请了静态的重入锁
    private static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                lock.lock();//在try块之前加锁
                try {
                    count++;
                } finally {
                    lock.unlock();//在finally块中释放锁
                }
            }
        });
        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 10000; i++) {
                lock.lock();
                try {
                    count++;
                } finally {
                    lock.unlock();
                }
            }
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        System.out.println(count);
    }
}

ReentrantLock 与 Synchronized

相同点:都用来实现同步,都是可重入锁

区别:

Synchronized

1)是 Java 内置的关键字,实现基本的同步机制

2)不支持超时,非公平,不可中断,不支持多条件

3)自动解锁

ReentrantLock

1)是 JUC 类库提供的,

2)支持设置超时时间,支持公平锁与非公平锁,可中断,支持多条件判断,可以避免死锁

3)需要手动解锁,

选择:

一般情况下用Synchronized,简单

复杂的情况用ReentrantLock,灵活,功能多

性能:Synchronized经过发展有四种等级的锁,性能与ReentrantLock相差不多

读写锁 ReentrantReadWriteLock

特性

1.读锁是共享锁,写锁是独享锁

2.支持公平锁/非公平锁、重入性

3.锁降级:

持有写锁的线程在释放写锁前获取读锁,从而在写操作完成后不必完全释放锁

(不支持锁升级)

ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
ReentrantReadWriteLock.WriteLock writeLock = lock.writeLock();
ReentrantReadWriteLock.ReadLock readLock = lock.readLock();

writeLock.lock(); // 获取写锁
try {
    // 执行写操作
    readLock.lock(); // 获取读锁
} finally {
    writeLock.unlock(); // 释放写锁
}
try {
    // 执行读操作
} finally {
    readLock.unlock(); // 释放读锁
}

原理

1)AQS 中高 16 位表示读状态,低 16 位表示写状态(源码中在释放写锁时,使用当前同步状态state直接减去写状态releases,原因正是写状态是由同步状态的低 16 位表示的)

2)当读锁已经被读线程获取或者写锁已经被其他写线程获取,则写锁获取失败

基本使用

示例:

实现了缓存读写、锁降级

public class CachedData {
    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    private Object data;
    private boolean cacheValid;

    // 检查缓存状态并读取最新数据
    public void processCachedData() {
        // 获取读锁
        rwl.readLock().lock();
        if (!cacheValid) {// 缓存失效
            // 获取读锁前必须先释放写锁
            rwl.readLock().unlock();
            rwl.writeLock().lock();
            try {
                // 再次检查状态,因为另一个线程可能已经获得了写锁并完成了写操作
                if (!cacheValid) {
                    data = fetchDataFromDatabase();// 读数据库
                    cacheValid = true;
                }
                // 锁降级:在释放写锁前先获取读锁
                rwl.readLock().lock();
            } finally {
                rwl.writeLock().unlock(); // 释放写锁
            }
        }

        try {
            use(data);// 输出数据
        } finally {
            rwl.readLock().unlock();// 释放读锁
        }
    }
}

等待通知条件 Condition

概念

1)Condition接口是 Java 并发编程中一个重要的组件,用于线程间的协调和通信

2)通常与锁一起使用,为线程提供等待某个条件变化时通知等待线程的机制

接口

await():线程等待直到被通知或者中断

awaitUninterruptibly():线程等待直到被通知,即使在等待时被中断也不会返回

await(long time, TimeUnit unit):线程等待指定的时间,或被通知,或被中断

awaitNanos(long nanosTimeout):线程等待指定的纳秒时间,或被通知,或被中断

awaitUntil(Date deadline):线程等待直到指定的截止日期,或被通知,或被中断

signal():唤醒一个等待的线程

signalAll():唤醒所有等待的线程

线程阻塞唤醒类 LockSupport

概念

提供了一组阻塞和唤醒线程的静态方法,它不依赖于同步块或特定的锁对象,可以灵活地构建更复杂的同步结构

注意:

void unpark(Thread thread):唤醒一个由park方法阻塞的线程。如果该线程未被阻塞,那么下一次调用park时将立即返回。这允许“先发制人”式的唤醒机制

Synchronized阻塞线程,会使线程进入 BLOCKED 状态,LockSupport会使线程进入 WAINTING 状态

使用

示例:

有 3 个独立的线程,一个只会输出 A,一个只会输出 B,一个只会输出 C,在三个线程启动的情况下,请用合理的方式让他们按顺序打印 ABCABC

public class ABCPrinter {
    private static Thread t1, t2, t3;

    public static void main(String[] args) {
        int times = 2;
        t1 = new Thread(() -> {
            for (int i = 0; i < times; i++) {
                LockSupport.park();
                System.out.print("A");
                LockSupport.unpark(t2);
            }
        });

        t2 = new Thread(() -> {
            for (int i = 0; i < times; i++) {
                LockSupport.park();
                System.out.print("B");
                LockSupport.unpark(t3);
            }
        });

        t3 = new Thread(() -> {
            for (int i = 0; i < times; i++) {
                LockSupport.park();
                System.out.print("C");
                LockSupport.unpark(t1);
            }
        });

        t1.start();
        t2.start();
        t3.start();

        // 主线程稍微等待一下,确保其他线程已经启动并且进入park状态。
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 启动整个流程
        LockSupport.unpark(t1);
    }
}
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇