【Java多线程】3——Lock API控制多线程

3 Lock API控制多线程

⭐⭐⭐⭐⭐⭐
Github主页👉https://github.com/A-BigTree
笔记仓库👉https://github.com/A-BigTree/tree-learning-notes
个人主页👉https://www.abigtree.top
⭐⭐⭐⭐⭐⭐


如果可以,麻烦各位看官顺手点个star~😊

如果文章对你有所帮助,可以点赞👍收藏⭐支持一下博主~😆


文章目录

  • 3 Lock API控制多线程
    • 3.1 HelloWorld
      • 3.1.1 买票
      • 3.1.2 需要注意的点
        • 确保锁被释放
        • 加锁和解锁操作对称执行
        • 避免锁对象的线程私有化
    • 3.2 Lock接口
    • 3.3 可重入锁
      • 3.3.1 基本用法
      • 3.3.2 验证可重入性
      • 3.3.3 `tryLock()`
      • 3.3.4 `tryLock(time,timeUnit)`
      • 3.3.5 公平锁
        • 概念
        • 代码
        • 使用建议
      • 3.3.6 `lockInterruptibly()`
        • 相应中断
        • synchronized方式下的阻塞状态无法打断
        • `lockInterruptibly()`
    • 3.4 读写锁
      • 3.4.1 读写锁介绍
        • 概念
        • 进入条件
        • 重要特征
      • 3.4.2 `ReadWriteLock`接口
      • 3.4.3 `ReentrantReadWriteLock`类结构
        • 总体结构图
      • 3.4.4 典型案例
    • 3.5 线程间通信
      • 3.5.1 核心语法
      • 3.5.2 案例演示
        • 代码实现
      • 3.5.3 定制化通信
        • 语法基础
        • 案例
    • 3.6 Lock与synchronized对比
      • 3.6.1 相同点
      • 3.6.2 不同点
      • 3.6.3 使用建议
        • 从功能效果的角度
        • 从开发便捷性的角度
        • 从性能角度
        • 使用建议

3.1 HelloWorld

3.1.1 买票

public class Demo01HelloWorld {

    // 声明成员变量维护票库存
    private int stock = 100;

    // 创建锁对象
    // 变量类型:java.util.concurrent.locks.Lock 接口
    // 对象类型:Lock 接口的最常用的实现类 ReentrantLock
    private Lock lock = new ReentrantLock();

    // 声明卖票的方法
    public void saleTicket() {
        try {

            // 加锁
            lock.lock(); // synchronized (this) {

            if (stock > 0) {
                // 卖票的核心操作
                System.out.println(Thread.currentThread().getName() + " 卖了一张,还剩 " + --stock + " 张票。");

            } else {

                System.out.println(Thread.currentThread().getName() + " 卖完了。");

            }

        } catch (Exception e) {
            e.printStackTrace();
        } finally {

            // 解锁
            lock.unlock(); // }

        }
    }

    public static void main(String[] args) {

        // 1、创建当前类对象
        Demo01HelloWorld demo = new Demo01HelloWorld();

        // 2、开启三个线程调用卖票方法
        new Thread(()->{
            for (int i = 0; i < 40; i++) {
                demo.saleTicket();
                try {
                    TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {}
            }
        }, "thread-01").start();

        new Thread(()->{
            for (int i = 0; i < 40; i++) {
                demo.saleTicket();
                try {
                    TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {}
            }
        }, "thread-02").start();

        new Thread(()->{
            for (int i = 0; i < 40; i++) {
                demo.saleTicket();
                try {
                    TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {}
            }
        }, "thread-03").start();
    }

}

3.1.2 需要注意的点

确保锁被释放

使用 Lock API 实现同步操作,是一种面向对象的编码风格。这种风格有很大的灵活性,同时可以在常规操作的基础上附加更强大的功能。但是也要求编写代码更加谨慎:如果忘记调用 lock.unlock() 方法则锁不会被释放,从而造成程序运行出错。

加锁和解锁操作对称执行

不管同步操作是一层还是多层,有多少个加锁操作,就应该相应的有多少个解锁操作。

避免锁对象的线程私有化

锁对象如果是线程内部自己创建的,是自己独占的,其它线程访问不到这个对象,那么这个锁将无法实现**『排他』**效果,说白了就是:锁不住。

3.2 Lock接口

全类名:java.util.concurrent.locks.Lock

方法功能说明:

方法名功能
void lock()加同步锁,如果没有得到锁会一直等
void unlock()解除同步锁
boolean tryLock()尝试获取锁。如果没有获取到则立即返回,不做任何等待
返回 true:表示获取成功
返回 false:表示获取失败
boolean tryLock(long time, TimeUnit unit)尝试获取锁,且等待指定时间
返回 true:表示获取成功
返回 false:表示获取失败
void lockInterruptibly()以『支持响应中断』的模式获取锁
Condition newCondition()获取用于线程间通信的 Condition 对象

3.3 可重入锁

全类名:java.util.concurrent.locks.ReentrantLock

这是 Lock 接口最典型、最常用的一个实现类。

3.3.1 基本用法

基本要求1:将解锁操作放在 finally 块中,确保解锁操作能够被执行到。

基本要求2:加锁和解锁操作要对称。

try {
    // 加锁
    lock.lock();
    
    // 同步代码部分
} catch(Exception e) {
    // ...
} finally {
    // 解锁
    lock.unlock();
}

3.3.2 验证可重入性

// 测试目标:验证可重入性
// 测试方式:在同一个线程内,嵌套使用 try ... catch ... finally 结构
// 由于可重入性的大前提就是已经加了一个锁,然后再加一个锁,所以不可能有多个线程,就在 main 线程里测试即可
// 测试标准:线程不会被自己锁住,不会陷入死锁就证明当前使用的 API 支持可重入
// 创建锁对象
Lock lock = new ReentrantLock();

try {
    // 外层加锁操作
    lock.lock();
    System.out.println(Thread.currentThread().getName() + " 外层加锁成功。");

    try {
        // 内层加锁操作
        lock.lock();
        System.out.println(Thread.currentThread().getName() + " 内层加锁成功。");
    } finally {
        // 内层解锁操作
        lock.unlock();
        System.out.println(Thread.currentThread().getName() + " 内层解锁成功。");
    }

} finally {
    // 外层解锁操作
    lock.unlock();
    System.out.println(Thread.currentThread().getName() + " 外层解锁成功。");
}

3.3.3 tryLock()

public class Demo03TryLock {

    private Lock lock = new ReentrantLock();

    public void showMessage() {

        boolean lockResult = false;

        try {

            // 尝试获取锁
            // 返回true:获取成功
            // 返回false:获取失败
            lockResult = lock.tryLock();

            if (lockResult) {
                try {
                    TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {}
                System.out.println(Thread.currentThread().getName() + " 得到了锁,正在工作");
            } else {
                System.out.println(Thread.currentThread().getName() + " 没有得到锁");
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {

            // 如果曾经得到了锁,那么就解锁
            if (lockResult) {
                lock.unlock();
            }

        }

    }

    public static void main(String[] args) {

        // 1、创建多个线程共同操作的对象
        Demo03TryLock demo = new Demo03TryLock();

        // 2、创建三个线程
        new Thread(()->{

            for(int i = 0; i < 20; i++) {
                try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {}
                demo.showMessage();
            }

        }, "thread-01").start();

        new Thread(()->{

            for(int i = 0; i < 20; i++) {
                try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {}
                demo.showMessage();
            }

        }, "thread-02").start();

        new Thread(()->{

            for(int i = 0; i < 20; i++) {
                try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {}
                demo.showMessage();
            }

        }, "thread-03").start();
    }

}

3.3.4 tryLock(time,timeUnit)

public class Demo04TryLockWithTime {

    private Lock lock = new ReentrantLock();

    // 得到锁之后占用 5 秒
    public void useLock() {

        try {

            lock.lock();

            System.out.println(Thread.currentThread().getName() + " 开始工作");
            try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {}
            System.out.println(Thread.currentThread().getName() + " 结束工作");

        } catch (Exception e) {
            e.printStackTrace();
        } finally {

            lock.unlock();

        }

    }

    // 在尝试获取锁的过程中,可以等待一定时间
    public void waitLock() {

        boolean lockResult = false;

        try {

            // 尝试获取锁,并指定了等待时间
            lockResult = lock.tryLock(3, TimeUnit.SECONDS);

            if (lockResult) {
                System.out.println(Thread.currentThread().getName() + " 得到了锁,开始工作");
            } else {
                System.out.println(Thread.currentThread().getName() + " 没有得到锁");
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            if (lockResult) {
                lock.unlock();
            }
        }

    }

    public static void main(String[] args) {

        // 1、创建当前类对象
        Demo04TryLockWithTime demo = new Demo04TryLockWithTime();

        // 2、创建 A 线程占用锁
        new Thread(()->{

            demo.useLock();

        }, "thread-a").start();

        // 3、创建 B 线程尝试获取锁
        new Thread(()->{

            demo.waitLock();

        }, "thread-b").start();
    }

}

3.3.5 公平锁

概念

ReentrantLock 构造器中传入 boolean 类型的参数:

  • true:创建公平锁(在锁上等待最长时间的线程有最高优先级);
  • false:创建非公平锁;
代码
public class Demo05FairLock {

    private Lock lock = new ReentrantLock(true);

    public void printMessage() {

        try {

            lock.lock();

            System.out.println(Thread.currentThread().getName() + " say hello to you");

            try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {}

        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

    }

    public static void main(String[] args) {

        // 1、创建当前类的对象
        Demo05FairLock demo = new Demo05FairLock();

        // 2、创建三个线程,每个线程内调用 printMessage() 方法十次
        new Thread(()->{

            for (int i = 0; i < 10; i++) {
                demo.printMessage();
            }

        }, "thread-a").start();

        new Thread(()->{

            for (int i = 0; i < 10; i++) {
                demo.printMessage();
            }

        }, "thread-b").start();

        new Thread(()->{

            for (int i = 0; i < 10; i++) {
                demo.printMessage();
            }

        }, "thread-c").start();


    }

}
使用建议
  • 公平锁对线程操作的吞吐量有限制,效率上不如非公平锁;
  • 如果没有特殊需要还是建议使用默认的非公平锁。

3.3.6 lockInterruptibly()

lock:动词,加锁的动作

Interruptibly:修饰动词的副词,表示可以被打断 组合起来的含义:以可以被打断的方式加锁。

具体来说就是如果线程是被 lockInterruptibly() 加的锁给阻塞的,那么这个阻塞状态可以被打断。

相应中断

在这里插入图片描述

synchronized方式下的阻塞状态无法打断

synchronized 导致的 blocked 状态不支持响应中断。

在这里插入图片描述

lockInterruptibly()

在这里插入图片描述

public class Demo07LockInterruptibly {

    private Lock lock = new ReentrantLock();

    // 小强:持续占用锁。
    public void useLock() {
        try {

            lock.lock();

            while (true) {
                System.out.println(Thread.currentThread().getName() + " 正在占用锁");
                try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {}
            }

        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    // 小明:痴痴地等待小强释放锁
    public void waitLock() {

        System.out.println(Thread.currentThread().getName() + " 线程启动了");

        try {

            // 通过 lockInterruptibly() 方法获取锁,在没有获取到锁的阻塞过程中可以被打断
            lock.lockInterruptibly();

            // ...

        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }

        System.out.println(Thread.currentThread().getName() + " 线程结束了");

    }

    public static void main(String[] args) {

        // 1、创建当前类对象
        Demo07LockInterruptibly demo = new Demo07LockInterruptibly();

        // 2、创建占用锁的线程(小强)
        new Thread(()->{

            demo.useLock();

        }, "thread-qiang").start();

        Thread thread = new Thread(() -> {

            demo.waitLock();

        }, "thread-ming");

        thread.start();

        try {
            TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {}

        // 打断小明线程的阻塞状态
        thread.interrupt();
    }
}

lockInterruptibly()模式下,被打断的线程,如果希望在被打断之后继续执行某些逻辑,那么可以在catch块编写。

3.4 读写锁

3.4.1 读写锁介绍

概念

在实际场景中,读操作不会改变数据,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作了。

针对这种场景,Java 的并发包提供了读写锁 ReentrantReadWriteLock,它表示两个锁,一个是读操作相关的锁,称为读锁,这是一种共享锁;一个是写相关的锁,称为写锁,这是一种排他锁,也叫独占锁、互斥锁。

进入条件

进入读锁条件:

  • 同一个线程内(可重入性角度):
    • 目前无锁:可以进入
    • 已经有读锁:可以进入
    • 已经有写锁:可以进入(锁可以降级,权限可以收缩)
  • 不同线程之间(排他性角度):
    • 其他线程已经加了读锁:可以进入
    • 其他线程已经加了写锁:不能进入

进入写锁条件:

  • 同一个线程内(可重入性角度):
    • 目前无锁:可以进入
    • 已经有读锁:不能进入(锁不能升级,权限不能扩大)
    • 已经有写锁:可以进入
  • 不同线程之间(排他性角度):
    • 其他线程已经加了读锁:不能进入
    • 其他线程已经加了写锁:不能进入
重要特征

公平选择性:

支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。

重进入:

读锁和写锁都支持线程重进入:

  • 同一个线程:加读锁后再加读锁
  • 同一个线程:加写锁后再加写锁

锁降级:

在同一个线程内:读锁不能升级为写锁,但是写锁可以降级为读锁。

3.4.2 ReadWriteLock接口

全类名:java.util.concurrent.locks.ReadWriteLock

源码如下:

public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading.
     */
    Lock readLock();
 
    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing.
     */
    Lock writeLock();
}

readLock() 方法用来获取读锁,writeLock() 方法用来获取写锁。也就是说将文件的读写操作分开,分成两种不同的锁来分配给线程,从而使得多个线程可以同时进行读操作。

该接口下我们常用的实现类是:java.util.concurrent.locks.ReentrantReadWriteLock

3.4.3 ReentrantReadWriteLock类结构

public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {

    /** 读锁 */
    private final ReentrantReadWriteLock.ReadLock readerLock;

    /** 写锁 */
    private final ReentrantReadWriteLock.WriteLock writerLock;

    final Sync sync;
    
    /** 使用默认(非公平)的排序属性创建一个新的 ReentrantReadWriteLock */
    public ReentrantReadWriteLock() {
        this(false);
    }

    /** 使用给定的公平策略创建一个新的 ReentrantReadWriteLock */
    public ReentrantReadWriteLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
        readerLock = new ReadLock(this);
        writerLock = new WriteLock(this);
    }

    /** 返回用于写入操作的锁 */
    public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
    
    /** 返回用于读取操作的锁 */
    public ReentrantReadWriteLock.ReadLock  readLock()  { return readerLock; }

    abstract static class Sync extends AbstractQueuedSynchronizer {}

    static final class NonfairSync extends Sync {}

    static final class FairSync extends Sync {}

    public static class ReadLock implements Lock, java.io.Serializable {}

    public static class WriteLock implements Lock, java.io.Serializable {}
}
总体结构图

在这里插入图片描述

3.4.4 典型案例

使用 ReentrantReadWriteLock 进行读和写操作

操作测试目标
场景一多个线程:同时获取读锁读锁可以共享
场景二多个线程:获取写锁写锁不能共享
场景三多个线程:一个线程先获取读锁后其他线程获取写锁读排斥写
场景四多个线程:一个线程获取写锁后其他线程获取读锁写排斥读
场景五同一个线程:获取读锁后再去获取写锁读权限不能升级为写权限
场景六同一个线程:获取写锁后再去获取读锁写权限可以降级为读权限
场景七同一个线程:获取读锁之后再去获取读锁读锁可重入
场景八同一个线程:获取写锁之后再去所获写锁写锁可重入

3.5 线程间通信

3.5.1 核心语法

  • ReentrantLock 同步锁:将执行操作的代码块设置为同步操作,提供原子性保证;
  • Condition 对象:对指定线程进行等待、唤醒操作;
    • await() 方法:让线程等待;
    • signal() 方法:将线程唤醒;
    • signalAll()方法:唤醒全部等待中的线程;

3.5.2 案例演示

代码实现
public class Demo03LockConditionWay {

    // 创建同步锁对象
    private Lock lock = new ReentrantLock();

    // 通过同步锁对象创建控制线程间通信的条件对象
    private Condition condition = lock.newCondition();

    private int data = 0;

    // 声明方法执行 + 1 操作
    public void doIncr() {

        try {

            // 使用 lock 锁对象加锁
            lock.lock();

            // 为了避免虚假唤醒问题:使用 while 结构进行循环判断
            // 判断当前线程是否满足执行核心操作的条件
            while (data == 1) {

                // 满足条件时,不该当前线程干活,所以进入等待状态
                condition.await();

            }

            // 不满足上面的条件时,说明该当前线程干活了,所以执行核心操作
            System.out.println(Thread.currentThread().getName() + " 执行 + 1 操作,data = " + ++data);

            // 自己的任务完成后,叫醒其它线程
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {

            // 释放锁
            lock.unlock();

        }
    }

    // 声明方法执行 - 1 操作
    public void doDecr() {

        try {

            // 使用 lock 锁对象加锁
            lock.lock();

            // 为了避免虚假唤醒问题:使用 while 结构进行循环判断
            // 判断当前线程是否满足执行核心操作的条件
            while (data == 0) {

                // 满足条件时,不该当前线程干活,所以进入等待状态
                condition.await();

            }

            // 不满足上面的条件时,说明该当前线程干活了,所以执行核心操作
            System.out.println(Thread.currentThread().getName() + " 执行 - 1 操作,data = " + --data);

            // 自己的任务完成后,叫醒其它线程
            condition.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {

            // 释放锁
            lock.unlock();

        }
    }

    public static void main(String[] args) {

        // 1、创建当前类的对象
        Demo03LockConditionWay demo = new Demo03LockConditionWay();

        // 2、创建四个线程,两个 + 1,两个 - 1
        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                demo.doIncr();
            }
        }, "thread-add A").start();

        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                demo.doDecr();
            }
        }, "thread-sub A").start();

        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                demo.doIncr();
            }
        }, "thread-add B").start();

        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                demo.doDecr();
            }
        }, "thread-sub B").start();

    }

  }

3.5.3 定制化通信

传统的 synchronized、wait()、notifyAll() 方式无法唤醒一个指定的线程。而 Lock 配合 Condition 的方式能够唤醒指定的线程,从而执行指定线程中指定的任务。

语法基础
  • ReentrantLock 同步锁:将执行操作的代码块设置为同步操作,提供原子性保证;
  • Condition 对象:对指定线程进行等待、唤醒操作;
    • await() 方法:让线程等待;
    • signal() 方法:将线程唤醒;
案例

要求:

要求四个线程交替执行打印如下内容:

  • 线程1:打印连续数字
  • 线程2:打印连续字母
  • 线程3:打印 * 符
  • 线程4:打印 $ 符

代码实现:

public class Demo03Condition {

    // 控制总体的操作步骤
    private int step = 1;

    // 负责打印数字的线程要打印的数字
    private int digital = 1;

    // 负责打印字母的线程要打印的字母
    private char alphaBet = 'a';

    // 同步锁对象
    private Lock lock = new ReentrantLock();

    // 条件对象:对应打印数字的线程
    private Condition conditionDigital = lock.newCondition();

    // 条件对象:对应打印字母的线程
    private Condition conditionAlphaBet = lock.newCondition();

    // 条件对象:对应打印星号的线程
    private Condition conditionStar = lock.newCondition();

    // 条件对象:对应打印 $ 的线程
    private Condition conditionDollar = lock.newCondition();

    // 声明一个方法专门打印数字
    public void printDigital() {
        try {
            lock.lock();

            // 只要 step 对 4 取模不等于 1,就不该当前方法干活
            while (step % 4 != 1) {

                // 使用专门的条件对象,让当前线程进入等待
                // 将来还用同一个条件对象,调用 singal() 方法就能精确的把这里等待的线程唤醒
                conditionDigital.await();
            }

            // 执行要打印的操作
            System.out.print(digital++);

            // 精准唤醒打印字母的线程
            conditionAlphaBet.signal();

            step++ ;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printAlphaBet() {
        try {
            lock.lock();

            while (step % 4 != 2) {
                conditionAlphaBet.await();
            }

            System.out.print(alphaBet++);

            conditionStar.signal();

            step++ ;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printStar() {
        try {
            lock.lock();

            while (step % 4 != 3) {
                conditionStar.await();
            }

            System.out.print("*");

            conditionDollar.signal();

            step++ ;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void printDollar() {
        try {
            lock.lock();

            while (step % 4 != 0) {
                conditionDollar.await();
            }

            System.out.println("$");

            conditionDigital.signal();

            step++ ;
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {

        Demo03Condition demo = new Demo03Condition();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                demo.printDigital();
            }
        }).start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                demo.printAlphaBet();
            }
        }).start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                demo.printStar();
            }
        }).start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                demo.printDollar();
            }
        }).start();

    }

}

3.6 Lock与synchronized对比

结论:在实际开发时,如果synchronized能够满足需要,那就使用synchronized,毕竟它自动加锁、解锁,代码简单。 如果synchronized无法满足需求,只能使用Lock。

3.6.1 相同点

  • 都支持独占锁

  • 都支持可重入

3.6.2 不同点

Lock 系列 API 用法synchronized 用法
加锁/解锁手动自动
支持共享锁×
支持尝试获取锁失败
后执行特定操作
×
灵活×
便捷×
响应中断lockInterruptibly()
方式支持阻塞状态响应中断
sleep()
睡眠后支持响应中断

被synchronized阻塞
不支持响应中断
代码风格面向对象面向过程
底层机制AQS(volatile + CAS + 线程的双向链表)= 非阻塞同步阻塞同步

3.6.3 使用建议

从功能效果的角度

Lock 能够覆盖 synchronized 的功能,而且功能更强大。

在这里插入图片描述

从开发便捷性的角度
  • synchronized:自动加锁、解锁,使用方便
  • Lock:手动加锁、解锁,使用不那么方便
从性能角度

二者差不多;

使用建议

synchronized 够用,那就使用 synchronized;如果需要额外附加功能则使用 Lock:

  • 公平锁
  • 共享锁
  • 尝试获取锁
  • 以支持响应中断的方式获取锁
  • ……

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/495684.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

第18次修改了可删除可持久保存的前端html备忘录

第17次修改了可删除可持久保存的前端html备忘录&#xff1a;增加年月日星期&#xff0c;增加倒计时&#xff0c;更改保存区名称可以多个备忘录保存不一样的信息&#xff0c;匹配背景主题&#xff1a;现代深色 <!DOCTYPE html> <html lang"zh"> <head&…

SpringBoot国际化配置流程(超详细)

前言 最新第一次在做SpringBoot的国际化&#xff0c;网上搜了很多相关的资料&#xff0c;都是一些简单的使用例子&#xff0c;达不到在实际项目中使用的要求&#xff0c;因此本次将结合查询的资料以及自己的实践整理出SpringBoot配置国际化的流程。 SpringBoot国际化 "i…

智慧公厕四大核心能力,赋能城市公共厕所智能化升级

公共厕所是城市基础设施中不可或缺的一部分&#xff0c;但由于传统的公共厕所在建设与规划上&#xff0c;存在一定的局限性&#xff0c;导致环境卫生差、管理难度大、使用体验不佳等问题&#xff0c;给市民带来了很多不便。而智慧公厕作为城市智能化建设的重要组成部分&#xf…

谈谈变压器中的位置编码

变压器中的位置编码 一、说明 在上一期的“Transformers for Everyone”系列中&#xff0c;我们介绍了 Transformer 的概念&#xff0c;并深入研究了第一个关键架构元素&#xff1a;输入嵌入。如果你错过了第一集&#xff0c;你可以通过阅读来赶上&#xff1a;适合所有人的变形…

【Linux】 gcc(linux下的编译器)程序的编译和链接详解

目录 前言&#xff1a;快速认识gcc 1. 程序的翻译环境和执行环境 2.编译和链接 2.1翻译环境 2.2编译环境 1. 预处理 gcc -E指令 test.c&#xff08;源文件&#xff09; -o test.i&#xff08;生成在一个文件中&#xff0c;可以自己指定&#xff09; 预处理完成之后就停下来&am…

状态压缩的三种模型

第一种类型&#xff08;摆放方块&#xff09;&#xff1a; 代码如下&#xff1a; #include<iostream> #include<climits> #include<algorithm> #include<cstring> #include<cstdio> #include<cmath> #include<queue> #include<s…

vue3+ts+element home页面侧边栏+头部组件+路由组件组合页面教程

文章目录 效果展示template代码script代码样式代码 效果展示 template代码 <template><el-container class"home"><el-aside class"flex" :style"{ width: asideDisplay ? 70px : 290px }"><div class"aside-left&q…

项目中自动引入神器 - unplugin-auto-import/unplugin-vue-components

前端 项目中 自动引入 神器 前言 在开发中&#xff0c;我们总喜欢站在巨人的肩膀上开发&#xff0c;比如用一些 框架&#xff1a;vue,react, 组件库&#xff1a;element&#xff0c;ant。 工具函数&#xff1a;axios&#xff0c;lodash 现在是模块化时代&#xff0c;我们…

基于SpringBoot后端实现连接MySQL数据库并存贮数据

目录 一、什么是MySQL数据库 二、基于SpringBoot框架连接MySQL数据库 1、首先添加MySQL依赖&#xff1a; 2、配置数据库连接&#xff1a; 3、创建实体类&#xff1a; 4、创建Repository接口&#xff1a; 5、使用Repository&#xff1a; 三、编写业务SQL语句 1、使用Spring Data…

浅模仿小米商城布局(有微调)

CSS文件 *{margin: 0;padding: 0;box-sizing: border-box; }div[class^"h"]{height: 40px; } div[class^"s"]{height: 100px; } .h1{width: 1528px;background-color: green; } .h11{background-color:rgb(8, 220, 8); } .h111{width: 683px;background-c…

异或和之和【蓝桥杯】/拆位+贡献法

异或和之和 拆位贡献法 思路&#xff1a;刚开始考虑遍历L和R&#xff0c;同时可以用一个二维数组存储算过的值&#xff0c;但是时间复杂度还是O(n^2)&#xff0c;所以这种还是要拆位和利用贡献法 可以对于每个位&#xff0c;一定区间内&#xff0c;如果有奇数个1则异或值为1&…

阿里云ECS选型推荐配置

本文介绍构建Kubernetes集群时该如何选择ECS类型以及选型的注意事项。 集群规格规划 目前在创建Kubernetes集群时&#xff0c;存在着使用很多小规格ECS的现象&#xff0c;这样做有以下弊端&#xff1a; 网络问题&#xff1a;小规格Worker ECS的网络资源受限。 容量问题&…

Cubase 8.0 下载地址及安装教程

Cubase是一款流行的音乐制作和音频录制软件&#xff0c;由德国公司Steinberg开发。它是一款专业级的数字音频工作站&#xff08;DAW&#xff09;&#xff0c;广泛应用于音乐制作、音频录制、混音和制作等领域。 Cubase提供了丰富的功能和工具&#xff0c;用于录制、编辑、混音…

ubuntu22.04物理机双系统手动分区

ubuntu22.04物理机双系统手动分区 文章目录 ubuntu22.04物理机双系统手动分区1. EFI系统分区2. 交换分区3. /根分区4. /home分区分区后的信息 手动分区顺序&#xff1a;EFI系统分区(/boot/efi)、交换分区(/swap)、/根分区、/home分区。 具体参数设置&#xff1a; 1. EFI系统分…

Java SPI 机制

SPI 机制的定义 在Java中&#xff0c;SPI&#xff08;Service Provider Interface&#xff09;机制是一种用于实现软件组件之间松耦合的方式。它允许在应用程序中定义服务接口&#xff0c;并通过在类路径中发现和加载提供该服务的实现来扩展应用程序功能。 SPI 机制通常涉及三…

霉霉说地道中文,口型、卡点几乎完美,网友:配音时代结束了?

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 新建了人工智能中文站 每天给大家更新可用的国内可用chatGPT资源 更多资源欢迎关注 「给电影配音的时代即将结束了。」 AI 的发展让很多人直呼饭碗被抢了&#xff0c;以前是艺术家、程序员…… 现在配音员也要失业了&a…

Docker部署一个SpringBoot项目(超级详细)

注意&#xff1a;下面的教程主要是针对 Centos7 的&#xff0c;如果使用的其他发行版会有细微的差别&#xff0c;请查看官方文档。 Docker部署一个SpringBoot项目&#xff08;超级详细&#xff09; 一、安装Docker1.卸载旧版2.配置Docker的yum库3.安装Docker4.设置开机自启动5.…

超级充电测试基础认识

超级充电技术是电动汽车&#xff08;EV&#xff09;充电技术的一种&#xff0c;它的主要目标是在最短的时间内为电动汽车充满电。这种技术的出现&#xff0c;对于推动电动汽车的普及和发展具有重要的意义。 超级充电测试是对超级充电设备和系统进行全面、深入的检查和评估&…

SQL107 将两个 SELECT 语句结合起来(二)(不用union,在where里用or)

select prod_id,quantity from OrderItems where quantity 100 or prod_id like BNBG% order by prod_id;在where子句里使用or

Windows 7 静态 IP 地址

Windows 7 静态 IP 地址 References 静态 IP 地址设置为 192.168.1.198 控制面板 -> 查看网络状态和任务 更改适配器设置 网络连接 -> 属性 TCP / IPv4 警告 - 已计划将多个默认网关用于提供单一网络 (例如 intranet 或者 Internet) 的冗余。 6.1. 关闭 redundancy VMw…
最新文章