首页 > 编程语言 >精通Java并发锁机制:24种锁技巧+业务锁匹配方案

精通Java并发锁机制:24种锁技巧+业务锁匹配方案

时间:2024-10-22 20:45:54浏览次数:11  
标签:24 种锁 synchronized lock void ReentrantLock 线程 Java public

在这里插入图片描述
在 Java 并发编程中,锁是确保线程安全、协调多线程访问共享资源的关键机制。从基本的 synchronized 同步关键字到高级的 ReentrantLock、读写锁 ReadWriteLock、无锁设计如 AtomicInteger,再到复杂的同步辅助工具如 CountDownLatchCyclicBarrierSemaphore,每种锁都针对特定的并发场景设计,以解决多线程环境下的同步问题。StampedLock 提供了乐观读锁和悲观写锁的选项,而 ConcurrentHashMapConcurrentLinkedQueue 等并发集合则通过内部机制优化了并发访问。了解不同锁的特点和适用场景,对于构建高效、稳定的并发应用程序至关重要。

1、java锁领域范围介绍

在这里插入图片描述

1.1 基本锁
  • synchronized:Java的内置锁机制,用于同步方法或代码块。
  • ReentrantLock:可重入的互斥锁,提供了与synchronized相比更灵活的锁定机制。
1.2 读写锁
  • ReadWriteLock:允许多个读操作并行执行,但写操作会阻塞所有其他读写操作。
  • StampedLock:一种新的读写锁,支持乐观读和悲观写,以及避免ABA问题的版本号。
1.3 乐观/悲观锁
  • 乐观锁:基于无锁的算法,通常使用版本号或时间戳来实现。
  • 悲观锁:假设会发生冲突并采取预防措施,通常通过锁定机制实现。
1.4 原子变量和无锁结构
  • AtomicIntegerAtomicLongAtomicReference:提供无锁的线程安全操作。
  • ConcurrentHashMap:一个线程安全的哈希表,使用CAS操作来保证线程安全。
1.5 条件变量和屏障
  • Lock.newCondition:与ReentrantLock配合使用,允许线程在某些条件满足之前挂起。
  • CountDownLatch:允许一个或多个线程等待一组操作完成。除了作为倒计时门闩使用,也可以作为一次性的屏障点。
  • CyclicBarrier:允许一组线程相互等待,直到所有线程都到达某个公共屏障点。除了作为循环屏障使用,也可以作为可重用的屏障点。
1.6 信号量
  • Semaphore:用于控制对有限资源的访问,允许一定数量的线程并发访问资源。
1.7 高级并发集合
  • ConcurrentHashMap:线程安全的哈希表。
  • ConcurrentSkipListMap:线程安全的有序映射。
  • ConcurrentLinkedQueue:线程安全的无界队列。
  • BlockingQueue 实现:如ArrayBlockingQueueLinkedBlockingQueue等,提供阻塞操作的队列。
1.8 锁策略和优化
  • 锁粗化:将多个细粒度锁合并为一个粗粒度锁。
  • 锁消除:通过编译器优化,消除一些不必要的锁。
  • 锁分割:将一个大锁分解成多个小锁,以提高并发性能。

2、锁详细分析

2.1. synchronized

synchronized 是 Java 中用于实现线程同步的关键字,它通过内置锁(也称为监视器锁 Monitor)来控制多个线程对共享资源的访问。
在这里插入图片描述

图解说明:
  • Java 线程:表示运行中的线程,它们可能需要访问共享资源。
  • Java 内存:表示Java程序使用的内存空间,包括堆和栈等。
  • 共享资源:表示被多个线程共享的数据,需要通过同步机制来保护。
  • 对象头:Java对象头包含多个信息,其中之一是 Mark Word,用于存储对象的同步信息。
  • Mark Word:存储对象的锁状态信息,如是否被锁定、锁的持有者等。
  • Monitor:内置锁的实现机制,也称为监视器锁,通过对象的 Mark Word 指向一个监视器对象。
综合说明:
  • 作用synchronized 是Java语言中用于实现线程同步的一种关键字,它可以用于修饰方法或代码块,确保在同一时刻最多只有一个线程可以执行该段代码。
  • 背景:在早期的Java版本中,为了简化线程同步的操作,避免程序员直接使用底层的同步原语(如互斥锁),Java提供了synchronized关键字,它基于JVM的内置锁(Monitor)实现。
  • 优点
    • 使用简单:只需通过关键字即可实现同步,无需手动获取和释放锁。
    • 保证内存可见性:自动处理内存的可见性问题,确保线程间的正确通信。
  • 缺点
    • 锁粒度较粗:只能锁定整个方法或代码块,缺乏灵活性。
    • 性能问题:在高并发环境下,可能导致线程频繁阻塞和唤醒,影响性能。
  • 场景:适用于锁竞争不激烈,且同步代码块较小的场景。
  • 业务举例:在银行账户系统中,当一个客户通过ATM机进行存款或取款操作时,需要确保账户余额的更新操作在同一时刻只能由一个线程执行,以防止资金数据的不一致。
使用方式:
// 同步方法案例
class Counter {
    private int count = 0;

    // 同步方法
    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}

// 同步代码块案例
class Account {
    private double balance;
    private final Object lock = new Object();

    public void deposit(double amount) {
        synchronized (lock) {
            balance += amount;
        }
    }

    public double getBalance() {
        synchronized (lock) {
            return balance;
        }
    }
}

// 同步静态方法案例
class Utility {
    private static int sharedResource = 0;

    // 同步静态方法
    public static synchronized void modifyResource() {
        sharedResource++;
    }

    public static synchronized int getSharedResource() {
        return sharedResource;
    }
}

// 同步实例方法(锁定调用实例)
class BankAccount {
    private double balance;

    public synchronized void deposit(double amount) {
        balance += amount;
    }

    public synchronized double getBalance() {
        return balance;
    }
}

// 同步实例方法(自定义锁对象)
class BankAccountWithCustomLock {
    private double balance;
    private final Object lock = new Object();

    public void deposit(double amount) {
        synchronized (lock) {
            balance += amount;
        }
    }

    public double getBalance() {
        synchronized (lock) {
            return balance;
        }
    }
}

// 测试类
public class SynchronizedDemo {
    public static void main(String[] args) {
        Counter counter = new Counter();
        counter.increment();
        System.out.println(counter.getCount());

        Account account = new Account();
        account.deposit(100.0);
        System.out.println(account.getBalance());

        Utility.modifyResource();
        System.out.println(Utility.getSharedResource());

        BankAccount bankAccount = new BankAccount();
        bankAccount.deposit(200.0);
        System.out.println(bankAccount.getBalance());

        BankAccountWithCustomLock bankAccountWithLock = new BankAccountWithCustomLock();
        bankAccountWithLock.deposit(300.0);
        System.out.println(bankAccountWithLock.getBalance());
    }
}
业务代码案例:

业务说明: 在一个在线银行系统中,客户可以通过各种电子渠道(如网上银行、移动应用等)进行资金操作,包括查看账户余额、存款、取款和转账等。系统需要确保所有这些操作在多用户并发访问时的数据一致性和准确性。

为什么需要 synchronized 技术: 在多线程环境中,多个用户可能同时对同一个账户进行操作。如果没有适当的同步机制,如 synchronized,那么两个并发操作可能会同时修改账户余额,导致数据不一致。例如,两个线程同时从同一个账户中扣款,如果没有同步控制,可能会导致账户余额被错误地减少两次。

没有 synchronized 技术会带来什么后果

  • 数据不一致:最直接和严重的后果是账户余额可能会不正确。多个线程同时修改同一个变量可能会导致最终值与预期不符。
  • 金融风险:在金融系统中,数据不一致可能导致严重的金融风险和损失,比如用户资金被错误地扣除或增加。
  • 信任危机:如果用户发现账户余额不准确,可能会对银行系统的信任度下降,导致客户流失。
  • 法律和合规问题:金融行业的数据准确性受到严格的法律和规章监管。数据不一致可能违反相关法规,导致法律责任和罚款。

代码实现:

public class BankAccount {
    private double balance;  // 账户余额

    // 构造函数
    public BankAccount(double initialBalance) {
        this.balance = initialBalance;
    }

    // 存款方法
    public synchronized void deposit(double amount) {
        // 增加余额
        balance += amount;
        System.out.println("Deposited " + amount + ". New balance is " + balance);
    }

    // 取款方法
    public synchronized void withdraw(double amount) {
        if (amount <= balance) {
            balance -= amount;
            System.out.println("Withdrew " + amount + ". Remaining balance is " + balance);
        } else {
            System.out.println("Insufficient funds for withdrawal!");
        }
    }

    // 获取余额
    public synchronized double getBalance() {
        return balance;
    }
}

// 测试类
public class BankDemo {
    public static void main(String[] args) {
        final BankAccount account = new BankAccount(1000); // 开始余额为1000

        // 创建线程进行存款
        Thread depositThread = new Thread(() -> {
            account.deposit(500);
        });

        // 创建线程进行取款
        Thread withdrawThread = new Thread(() -> {
            account.withdraw(200);
        });

        depositThread.start();
        withdrawThread.start();
    }
}
2.2. ReentrantLock

ReentrantLock 是 Java 中一个高级同步工具,它提供了比 synchronized 更多的灵活性和控制能力。这个锁基于 Java 的 java.util.concurrent.locks.Lock 接口实现,通常内部使用 AQS(AbstractQueuedSynchronizer)来管理锁的获取和释放。
在这里插入图片描述

图解说明:
  • Java 线程:表示运行中的线程,它们可能需要访问共享资源。
  • ReentrantLock 实例:是 ReentrantLock 类的实例,用于控制对共享资源的访问。
  • AQS(同步队列)AbstractQueuedSynchronizer 的实例,用于管理锁的请求和释放。它维护一个同步队列,该队列保存所有请求锁的线程。
  • 共享资源:表示被多个线程共享的数据,需要通过锁来保护以确保线程安全。
  • 线程等待队列:当锁不可用时,请求锁的线程可能会被放入等待队列。
  • 锁状态:表示当前锁的状态,如锁定、未锁定、重入次数等。
  • 条件变量ReentrantLock 可以与一个或多个条件变量关联,用于更复杂的线程间协调。
  • 公平锁标志:指示锁是否以公平模式运行,公平模式下,锁会按照线程请求的顺序分配。
  • 独占模式ReentrantLock 默认以独占模式运行,即一次只允许一个线程持有锁。
  • 重入次数:记录当前线程获取锁的次数,允许同一线程多次获取锁。
  • 队列头节点:指向同步队列中的第一个节点,通常是获取锁的线程节点。
综合说明:
  • 作用ReentrantLock 是Java并发包中的一个类,提供了与synchronized关键字类似的同步功能,但它提供了更丰富的API,如尝试非阻塞获取锁、可中断获取锁、超时获取锁等。
  • 背景:随着多核处理器的普及和Java应用对并发性能要求的提高,synchronized的局限性逐渐显现,因此Java并发包提供了ReentrantLock,以提供更灵活的线程同步机制。
  • 优点
    • 功能丰富:支持多种锁获取方式,包括可中断、超时、尝试非阻塞获取等。
    • 公平锁:可以设置为公平锁,以减少线程饥饿问题。
  • 缺点
    • 使用复杂:需要手动获取和释放锁,增加了编码复杂度。
    • 必须手动释放:如果异常处理不当,可能导致锁无法释放。
  • 场景:适用于需要精细控制锁或需要高级功能的场景,如可中断锁获取、尝试非阻塞获取等。
  • 业务举例:在高并发的电商平台中,ReentrantLock 可以用于实现一个线程安全的购物车系统,其中锁的灵活性和高级功能有助于提高系统的响应速度和用户体验。例如,当多个用户同时尝试购买同一件库存有限的商品时,ReentrantLock可以确保商品库存的更新操作是原子性的,避免超卖现象。
使用方式:
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;

public class Counter {
    // 定义一个 ReentrantLock 对象作为锁
    private final ReentrantLock lock = new ReentrantLock();
    // 用于计数的共享资源
    private int count = 0;

    // 锁的获取与释放
    public void increment() {
        // 获取锁
        lock.lock();
        try {
            // 对共享资源进行操作,此处为增加计数
            count++;
            // 打印更新后的计数
            System.out.println("Count updated to " + count);
        } finally {
            // 确保在操作完成后释放锁
            lock.unlock();
        }
    }

    public int getCount() {
        // 获取锁以确保线程安全的读取
        lock.lock();
        try {
            // 返回当前计数
            return count;
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    // 尝试非阻塞地获取锁
    public void tryIncrement() {
        // 尝试获取锁,如果锁不可用则立即返回false
        if (lock.tryLock()) {
            try {
                // 如果成功获取锁,则增加计数
                count++;
                // 打印更新后的计数
                System.out.println("Count updated to " + count);
            } finally {
                // 释放锁
                lock.unlock();
            }
        } else {
            // 如果未能获取锁,打印提示信息
            System.out.println("Unable to acquire lock");
        }
    }

    // 带超时的锁获取
    public void incrementWithTimeout(long timeout, TimeUnit unit) {
        // 尝试在指定的时间内获取锁
        if (lock.tryLock(timeout, unit)) {
            try {
                // 如果成功获取锁,则增加计数
                count++;
                // 打印更新后的计数
                System.out.println("Count updated to " + count);
            } finally {
                // 释放锁
                lock.unlock();
            }
        } else {
            // 如果在指定时间内未能获取锁,打印提示信息
            System.out.println("Unable to acquire lock within timeout");
        }
    }

    // 可中断地获取锁
    public void incrementInterruptibly() throws InterruptedException {
        // 可中断地获取锁
        lock.lockInterruptibly();
        try {
            // 如果成功获取锁,则增加计数
            count++;
            // 打印更新后的计数
            System.out.println("Count updated to " + count);
        } finally {
            // 释放锁
            lock.unlock();
        }
    }
}

public class BoundedBuffer {
    private final ReentrantLock lock = new ReentrantLock();
    // 定义两个条件变量,一个用于缓冲区不满,一个用于缓冲区不空
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();
    private final Object[] items = new Object[100]; // 存储数据的数组
    private int putPtr, takePtr, count; // 指向待放入位置的指针、指向待取出位置的指针和计数器

    public void put(Object x) throws InterruptedException {
        // 获取锁
        lock.lock();
        try {
            // 使用循环等待缓冲区不满的条件
            while (count == items.length) {
                notFull.await();
            }
            // 放入元素
            items[putPtr] = x;
            // 更新放入位置的指针
            putPtr = (putPtr + 1) % items.length;
            // 更新计数器
            count++;
            // 通知可以取元素的条件变量
            notEmpty.signal();
        } finally {
            // 释放锁
            lock.unlock();
        }
    }

    public Object take() throws InterruptedException {
        // 获取锁
        lock.lock();
        try {
            // 使用循环等待缓冲区不空的条件
            while (count == 0) {
                notEmpty.await();
            }
            // 取出元素
            Object x = items[takePtr];
            // 更新取出位置的指针
            takePtr = (takePtr + 1) % items.length;
            // 更新计数器
            count--;
            // 通知可以放元素的条件变量
            notFull.signal();
            // 返回取出的元素
            return x;
        } finally {
            // 释放锁
            lock.unlock();
        }
    }
}

// 测试类
public class ReentrantLockDemo {
    public static void main(String[] args) throws InterruptedException {
        Counter counter = new Counter();
        counter.increment(); // 同步方法,用于增加计数
        System.out.println("Current count: " + counter.getCount()); // 同步方法,用于获取当前计数

        counter.tryIncrement(); // 尝试非阻塞地获取锁并增加计数
        counter.incrementWithTimeout(2, TimeUnit.SECONDS); // 尝试在指定时间内获取锁并增加计数

        BoundedBuffer buffer = new BoundedBuffer(); // 创建有界缓冲区
        buffer.put("Item 1"); // 生产者放入元素
        Object item = buffer.take(); // 消费者取出元素
        System.out.println("Taken from buffer: " + item);
    }
}
业务代码案例:

业务说明: 在线交易平台允许多个用户同时提交订单。每个订单都需要检查库存、扣减库存数量、生成订单记录并更新用户余额。这些操作需要作为一个原子操作来处理,以确保库存数据的一致性和避免超卖现象。

为什么需要 ReentrantLock 技术: 在高并发的环境下,多个用户可能同时对同一商品提交订单。使用 ReentrantLock 可以确保在检查和扣减库存的过程中,只有一个请求能够操作共享资源,避免了并发导致的数据不一致问题。

没有 ReentrantLock 技术会带来什么后果: 如果没有适当的同步机制,如 ReentrantLock,可能会导致库存数据被多个请求同时修改,从而出现超卖现象。这不仅会损害消费者体验,还可能导致财务损失和信誉下降。

代码实现:

import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;

public class OrderService {
    private final ReentrantLock lock = new ReentrantLock();
    private final InventoryService inventoryService;
    private final AccountService accountService;

    public OrderService(InventoryService inventoryService, AccountService accountService) {
        this.inventoryService = inventoryService;
        this.accountService = accountService;
    }

    public void processOrder(int productId, int quantity, double price) throws InterruptedException {
        lock.lock();
        try {
            // 检查库存是否充足
            if (inventoryService.checkInventory(productId, quantity)) {
                // 扣减库存
                inventoryService.decreaseInventory(productId, quantity);
                // 更新用户余额
                accountService.updateBalance(userId, price * quantity);
                // 创建订单记录
                createOrderRecord(productId, quantity, price);
                System.out.println("Order placed successfully for product ID: " + productId);
            } else {
                System.out.println("Not enough inventory for product ID: " + productId);
            }
        } finally {
            lock.unlock();
        }
    }

    private void createOrderRecord(int productId, int quantity, double price) {
        // 实现订单记录的创建逻辑
    }

    // 模拟库存服务
    static class InventoryService {
        public boolean checkInventory(int productId, int quantity) {
            // 实现检查库存逻辑
            return true;
        }

        public void decreaseInventory(int productId, int quantity) {
            // 实现扣减库存逻辑
        }
    }

    // 模拟账户服务
    static class AccountService {
        public void updateBalance(int userId, double amount) {
            // 实现更新账户余额逻辑
        }
    }
}

// 测试类
public class交易平台Demo {
    public static void main(String[] args) throws InterruptedException {
        InventoryService inventoryService = new OrderService.InventoryService();
        AccountService accountService = new OrderService.AccountService();
        OrderService orderService = new OrderService(inventoryService, accountService);

        // 模拟多个用户同时下单
        Thread t1 = new Thread(() -> orderService.processOrder(1, 2, 10.0));
        Thread t2 = new Thread(() -> orderService.processOrder(1, 3, 10.0));
        t1.start();
        t2.start();
        t1.join();
        t2.join();
    }
}

其他内容在第二篇文章《精通Java并发锁机制:24种锁技巧+业务锁匹配方案(第二部分)》中。。。

标签:24,种锁,synchronized,lock,void,ReentrantLock,线程,Java,public
From: https://blog.csdn.net/alises1314/article/details/143117711

相关文章

  • 近几年CSP-S考点分析与2024预测
    考点概率2020T1模拟、数学、二分一道很好的思维题,当年的T1比T2要难。T2贪心、位运算当年最简单的一道题,思维难度也不高。T3dp、topo要对题目进行转化,变成一个本质相同但难度不同的问题。T4队列、贪心类似于对于题意进行模拟。2021T1堆、贪心直接对整个进行贪心就......
  • NewStar2024-week3-Crypto
    古典密码不想看而且最近很忙,wp就贴exp了Crypto不用谢喵fromCrypto.CipherimportAESfromCrypto.Util.numberimport*importosKEY=b"fake_key_fake_ke"FLAG="flag{fake_flag_fake_flag}"defdecrypt(c):AES_ECB=AES.new(KEY,AES.MODE_ECB)......
  • 20241022每日一题洛谷P1223
    普及洛谷P1223接水问题有n个人在一个水龙头前排队接水,假如每个人接水的时间为Ti,请编程找出这n个人排队的一种顺序,使得n个人的平均等待时间最小第一行为一个整数n,第二行n个整数,第i个整数Ti表示第i个人的接水时间Ti输出两行,第一行为一种平均时间最短的排队顺......
  • NOIP2024集训Day58 字符串
    NOIP2024集训Day58字符串C.[CEOI2011]Matching发现要做的是排名串的匹配。考虑把它转成这个位置之前有多少个数小于当前这个数,这样就只要每个位置都对应相等的,那就一定是合法的。然后就可以类似KMP的预处理出一个\(nxt\)数组,然后再类似KMP的匹配。因为需要支持动态......
  • 2024版最新大模型场景应用汇总(持续更新)零基础入门到精通,收藏这篇就够了
    一、应用场景1.办公场景智能办公:文案生成(协助构建大纲+优化表达+内容生成)、PPT美化(自动排版+演讲备注+生成PPT)、数据分析(生成公式+数据处理+表格生成)。智能会议:会议策划(生成会议环节+会议分论坛+会议时间+会议预算)、同声传译(实时的多语言互译)、会议记录(生成结构清明+要......
  • 黑马程序员Java进阶学习(三)
    异常Java的异常体系异常的基本处理异常的作用异常是什么?异常是代码在编译或者执行的过程中可能出现的错误。异常的代表是谁?分为几类?Exception,分为两类:编译时异常、运行时异常。编译时异常:没有继承RuntimeExcpetion的异常,编译阶段就会出错。运行时异常:继承自Runtim......
  • 【java】抽象类和接口(了解,进阶,到全部掌握)
    各位看官早安午安晚安呀如果您觉得这篇文章对您有帮助的话欢迎您一键三连,小编尽全力做到更好欢迎您分享给更多人哦大家好我们今天来学习Java面向对象的的抽象类和接口,我们大家庭已经来啦~一:抽象类1.1:抽象类概念在面向对象的概念中,所有的对象都是通过类来描绘的,但是......
  • java的三大程序结构
    JAVA的三大程序结构一:顺序结构程序走上执行到下。二:选择结构if单选择结构if(布尔表达式){//如果布尔表达式的值为ture则执行{}里的语句块}publicclassIfDemo01{publicstaticvoidmain(String[]args){//接收键盘输入Scannerscanner=newSca......
  • 2024年TI杯E题-三子棋游戏装置方案分享-jdk123团队-第四弹 第一题
    #1024程序员节|征文#往期回顾前期准备摄像头bug解决手搓机械臂视觉模块的封装第一问:需要将一颗黑棋,放入棋盘中的五号位置。理想思路:依据摄像头,依据机械臂及其传感器。建立机械臂的逆运动学方程。然后完成精准定位,考虑到手搓机械臂的不稳定性。以及摄像头的精度。......
  • jsp高校毕业生就业信息管理系统k7241(程序+源码+数据库+调试部署+开发环境)
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表学生,教师,企业,岗位信息,招聘信息,应聘信息,就业情况,就业协议,问题反馈,反馈回复开题报告内容一、项目背景面对日益严峻的高校毕业生就业形势,传统的人工就业......