自学内容网 自学内容网

Java 中Lock接口锁的使用

目录

一. Lock接口下的实现类

1. ReentrantLock可重入锁

1.1. 可重入锁的原理

1.2. ReentrantLock的特点

1.3. 使用注意

1.4. 代码示例

2. ReentrantReadWriteLock读写锁

2.1. 实现原理

2.2. 注意事项

2.3. 代码示例


一. Lock接口下的实现类

在Java中,Lock 接口是 java.util.concurrent.locks 包中的一部分,它提供了比 synchronized 更丰富的锁操作。Lock 接口的实现类包括 ReentrantLock(可重入锁)、ReadWriteLock(读写锁) 等。

1. ReentrantLock可重入锁

ReentrantLock 是 Java 中 java.util.concurrent.locks 包下的一个可重入锁实现。可重入锁意味着一个线程可以多次获得同一把锁,而不会产生死锁。

1.1. 可重入锁的原理

  • 线程安全性:确保只有一个线程可以同时访问锁保护的资源。
  • 可重入性:同一个线程可以多次获得同一把锁,每获得一次锁,锁的持有计数器就增加一次,只有当锁的持有计数器为零时,其他线程才有机会获得这把锁。

1.2. ReentrantLock的特点

  • 公平性选择:可以选择公平锁或非公平锁。公平锁按照线程请求锁的顺序来分配锁,非公平锁则可能让等待时间较短的线程优先获得锁。
  • 条件变量支持:提供了与锁相关联的条件变量,用于实现等待/通知机制。
  • 中断处理:支持锁的中断获取,可以在获取锁的过程中响应中断。

1.3. 使用注意

  • 锁的释放:确保在 finally 块中释放锁,以避免因异常导致锁无法释放。
  • 避免死锁:即使 ReentrantLock 是可重入的,也需要小心避免死锁,例如,确保获取锁的顺序一致。
  • 性能考虑:锁操作有一定的性能开销,需要根据实际情况权衡使用锁的范围和粒度。
  • 默认创建非公平锁:如果传入true则创建的是公平锁 ReentrantLock lock = new ReentrantLock(true);

1.4. 代码示例

  • 测试代码
package com.demo.jucdemo;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 文件名:ReentrantLockExample
 * 创建者:
 * 创建时间:2024-09-21
 * 描述:模拟银行 存款,取款操作
 */
public class ReentrantLockDemo {
    public static void main(String[] args) {
        //初始账户为余额为0
        AccuntInfoRL accuntInfoRL = new AccuntInfoRL("小明",0);
        //循环启动线程
        for (int i = 0; i < 10; i++) {
            //模运算来判断是存款还是取款
            if (i % 2 == 0) {
                new Thread(() -> {
                    accuntInfoRL.save(10);
                }, "存款").start();
            } else {
                new Thread(() -> {
                    accuntInfoRL.withdrawal(10);
                }, "取款").start();
            }
        }
    }
}

/**
 * 模拟测试用户
 * 提供两个方法,分别是取款,存款
 */
class AccuntInfoRL{
    private String name;
    private int asset;
    AccuntInfoRL(String name,int asset) {
        this.name = name;
        this.asset = asset;
    }

    /**
     * 创建可重入锁时,默认是非公平锁
     * new ReentrantLock(true) 传入ture时创建的是公平锁
     */
    ReentrantLock lock = new ReentrantLock();
    /**
     * 存款方法
     * @param asset
     */
    public void save(int asset) {
        //1.加锁
        lock.lock();
        try {
            //2.业务代码
            this.asset += asset;
            System.out.println(this.name+"-存款:"+asset +" || 余额:"+this.asset);
        } catch (Exception e) {
            throw new RuntimeException("存款失败");
        }finally {
            //3.释放锁
            lock.unlock();
        }
    }
    /**
     * 取款方法
     */
    public void withdrawal(int asset){
        //1.加锁
        lock.lock();
        try {
            //2.业务代码
            //先判断金额是否足够
            if(asset <= this.asset){
                this.asset -= asset;
                System.out.println(this.name+"-取款:"+asset+" || 余额:"+this.asset);
            }else {
                //如果余额不足直接打印,不做操作
                System.out.println("余额不足,取款失败");
            }
        } catch (Exception e) {
            throw new RuntimeException("取款失败");
        }finally {
            //3.释放锁
            lock.unlock();
        }
    }
}
  • 测试结果

2. ReentrantReadWriteLock读写锁

ReentrantReadWriteLock 是 Java 中实现读写锁的一个类,它支持一个读锁和一个写锁。读锁可以由多个线程同时持有,而写锁是排他的,一次只能由一个线程持有。这种锁非常适合读多写少的场景,因为它允许多个线程同时进行读操作,从而提高了程序的并发性能。

Lock 接口的读写锁是通过 ReentrantReadWriteLock 内部静态类实现的

ReentrantReadWriteLock.WriteLock.lock() // 写锁

ReentrantReadWriteLock.ReadLock.lock() // 读锁

2.1. 实现原理

  1. 锁的分离ReentrantReadWriteLock 将锁分为读锁和写锁,读锁可以由多个线程共享,写锁则是排他的。

  2. 可重入:无论是读锁还是写锁,都支持可重入性,即同一个线程可以多次获取同一把锁。

  3. 锁状态:锁的状态由一个整型变量 state 表示,其中高16位表示读锁的计数,低16位表示写锁的计数。

  4. 公平性ReentrantReadWriteLock 可以是公平的也可以是非公平的。公平锁保证线程获取锁的顺序是公平的,而非公平锁则可能让某些线程优先获取锁。

  5. 锁的获取

    • 读锁获取:如果写锁没有被占用,线程可以获取读锁,并且读锁可以被多个线程共享。
    • 写锁获取:如果读锁和写锁都没有被占用,线程可以获取写锁。
  6. 锁的释放

    • 读锁释放:释放读锁时,读锁计数减一,如果读锁计数为零,则可能允许等待的写锁获取锁。
    • 写锁释放:释放写锁时,写锁计数减一,并且唤醒等待的读锁或写锁。
  7. 锁的降级:锁降级是允许的,即线程可以先释放写锁,再获取读锁。

  8. 锁的升级:锁升级是不允许的,即线程不能先获取读锁,再获取写锁,因为这可能导致死锁。

2.2. 注意事项

  • 锁的公平性:公平锁可能会降低性能,但可以避免线程饥饿问题。
  • 锁的升级:避免在持有读锁的情况下尝试获取写锁,因为这可能导致死锁。
  • 锁的释放:确保在 finally 块中释放锁,以避免死锁。

2.3. 代码示例

  1. 创建银行账户类
    package com.demo.jucdemo;
    
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    /**
     * 文件名:AccountInfo
     * 创建者:
     * 创建时间:2024-08-22
     * 描述: 银行账户信息,存款方法,取款方法,查询账户余额方法
     */
    public class AccountInfo {
        private String name;
        private int asset;
        AccountInfo(String name, int asset) {
            this.name = name;
            this.asset = asset;
        }
        //创建读写锁
        ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
        /**
         * 存款方法,需要使用写锁
         * @param asset
         */
        public void save(int asset) {
            //1.获取写锁
            this.reentrantReadWriteLock.writeLock().lock();
            try {
                //2.业务代码
                this.asset += asset;
                System.out.println(this.name+"-存款:"+asset +" || 余额:"+this.asset);
            } catch (Exception e) {
                throw new RuntimeException("存款失败");
            }finally {
                //释放锁
                this.reentrantReadWriteLock.writeLock().unlock();
            }
        }
        /**
         * 查询账户余额方法
         * 需要使用读锁,
         * @return
         */
        public AccountInfo queryRemaining(){
            //1.获取读锁
            this.reentrantReadWriteLock.readLock().lock();
            try {
                //2.业务处理
                return new AccountInfo(this.name,this.asset);
            } catch (Exception e) {
                return null;
            }finally {
                //3.释放读锁
                this.reentrantReadWriteLock.readLock().unlock();
            }
        }
        /**
         * 取款方法
         * 账户减钱使用写锁
         */
        public void withdrawal(int asset){
            //1.获取写锁
            this.reentrantReadWriteLock.writeLock().lock();
            try {
                //2.业务处理
                //先判断金额是否足够
                if(asset <= this.asset){
                    this.asset -= asset;
                    System.out.println(this.name+"-取款:"+asset+" || 余额:"+this.asset);
                }else {
                    //如果余额不足直接打印,不做操作
                    System.out.println("余额不足,取款失败");
                }
            } catch (Exception e) {
                throw new RuntimeException("取款失败");
            }finally {
                //3.释放写锁
                this.reentrantReadWriteLock.writeLock().unlock();
            }
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }
        public int getAsset() {
            return asset;
        }
        public void setAsset(int asset) {
            this.asset = asset;
        }
    }
    
  2. 创建测试类
    package com.demo.jucdemo;
    
    /**
     * 文件名:Main
     * 创建者:
     * 创建时间:2024-08-19
     * 描述:测试类
     */
    public class Main {
        public static void main(String[] args) {
            //初始化账户信息,金额为0
            AccountInfo account = new AccountInfo("小红", 0);
    
            //循环启动线程
            for (int i = 0; i < 10; i++) {
                //1.开启个新线程查询余额
                new Thread(() -> {
                    //取款之后查询余额
                    AccountInfo accountInfo = account.queryRemaining();
                    System.out.println("查询账户余额:"+"账户名称:"+accountInfo.getName()+" || 账户余额:"+accountInfo.getAsset());
                }, "查询余额").start();
    
                //2.模运算来判断是存款还是取款
                if (i % 2 == 0) {
                    new Thread(() -> {
                        account.save(10);
                    }, "存款").start();
                } else {
                    new Thread(() -> {
                        account.withdrawal(10);
                    }, "取款").start();
                }
            }
        }
    }
    
  3. 测试结果


原文地址:https://blog.csdn.net/weixin_39865508/article/details/142423945

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!