Java中的显式锁

本篇主要介绍Java中显式锁的内容

  • 在Java中,显式锁(Explicit Lock)提供了一种比synchronized关键字更灵活的线程同步机制。Java的显式锁由java.util.concurrent.locks包提供,最常用的显式锁是ReentrantLock它是一个可重入锁,类似于synchronized关键字,但提供了更多的功能和灵活性。;

1. 基本用法

  • 用于对变量进行访问的保护,以确保在多线程环境下,线程运行是安全的;
  • 具体做法:
    //引入两个必要的库和里边的类 
    import java.util.concurrent.locks.Lock;  
    import java.util.concurrent.locks.ReentrantLock;
    ……
    //在设置变量之后,声明建立一个ReentrantLock类锁,并由Lock创建的对象接收  
    Lock lock = new ReentrantLock();  
    ……
    //在需要的时候获取锁
    locks.lock();  
    try{  
        代码块;
    }finally{  
        lock.unlock();//用finally的原因是确保在任何情况下都能释放锁,不至于出现死锁的情况。
    }  
    //此时,如果有多个方法都在使用相应的变量(体现在代码块中),将获取锁操作视作p操作,释放锁操作视为v操作,则下一个线程/方法在进行操作之前只能等前一个线程释放锁,即使数目上去了,结果也是如此。
    

2. 高级功能

2.1 可中断的锁获取

  • 可以使用ReentrantLock尝试获取锁,并在等待锁的过程中响应中断(中断服务程序);
  • 这个过程中使用lock.lockInterruptibly()方法,它也是ReentrantLock提供的,但与.lock()不同,它在获取锁的过程中会响应中断如果当前线程在等待获取锁时被中断,那么该方法会抛出InterruptedException异常。(等于是线程a和线程b,两者都使用了lockInterruptibly。当其中一个获取到锁后,引发中断,在等待锁的另一个线程被中断后会自动抛出InterruptedException异常直至锁被重新释放后解除中断)。本质上不是用等待而是用中断的方式完成使用锁的排他,但得注意要有try--finally块保证任何情况在都能正确释放锁,不然很快会死锁;
  • 实例:
    //对中断锁的设置
    public void safeIncrement() throws InterruptedException {
        lock.lockInterruptibly();  // 可中断地获取锁
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }
    //一种情况举例
    Counter counter = new Counter();
    
    Runnable task = () -> {
        try {
            counter.safeIncrement();
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            System.out.println("Thread was interrupted");
        }
    };
    
    Thread t1 = new Thread(task);
    Thread t2 = new Thread(task);
    
    t1.start();
    t2.start();
    
    

2.2 尝试获取锁

  • 使用Locks类的.trylock()方法可以尝试获取锁,并在获取不到时立即返回

try后边可以同时跟catch和finally,两者发挥的功能不同,一个是抛出异常的说明,另一个是兜底保证代码中是能被执行

  • 实例
    public boolean tryIncrement() {
        if (lock.tryLock()) {  // 尝试获取锁
            try {
                count++;
                return true;
            } finally {
                lock.unlock();
            }
        }
        return false;  // 获取锁失败
    }
    
    

2.3 带超时的尝试获取锁

  • 是一种在多线程编程中用于提高灵活性和响应能力的方法;
  • 通过这种方法,线程在尝试获取锁时可以指定一个超时时间,如果在指定时间内未能获取锁,线程可以执行其他操作或进行适当的处理,而不是无限期地等待锁
  • 在Java中,可以使用ReentrantLock类的tryLock(long timeout, TimeUnit unit)方法来实现带超时的尝试获取锁;

实现步骤:

  1. ReentrantLock()定义锁;
  2. tryLock(时间数目,时间单位)尝试在指定时间内获取锁并传值给一个布尔型变量存储,如果成功了返回true,如果失败了返回false;
  3. 如果获取锁成功了,则执行临界区的代码;反之如果失败了,执行其他操作(如记录日志或者返回特定结果);
  4. 如果在等待锁的过程中被中断了,则抛出InterruptionException异常,并在catch块中处理该异常,调用Thread.currentThread().interrupt()方法恢复中断状态;
  5. 无论成功与否,最后确保释放锁,用到finally块,.unlock()操作只有当成功取到锁后才会被调用。

一个实例:

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

public class Counter {
    private int count = 0;
    private final Lock lock = new ReentrantLock();//获取锁

    public boolean tryIncrementWithTimeout() {
        boolean acquired = false;//默认无锁
        try {
            acquired = lock.tryLock(1, TimeUnit.SECONDS);  // 尝试在1秒内获取锁
            if (acquired) {
                count++;
                return true;
            } else {
                System.out.println("Could not acquire lock within the specified time.");
                return false;
            }//成功或者失败的情况的处理方式
        //遇到中断,抛出异常并使用类方法恢复
        } catch (InterruptedException e) {
            System.out.println("Thread was interrupted while waiting for the lock.");
            Thread.currentThread().interrupt();
            return false;
        } finally {
            if (acquired) {
                lock.unlock();  // 确保锁被释放
            }
        }
    }

    public int getCount() {
        return count;
    }
    //标准测试的主函数
    public static void main(String[] args) {
        Counter counter = new Counter();
        
        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                counter.tryIncrementWithTimeout();
            }
        };//Lambda表达式,接口实体化

        //将接口转为线程后建立对象
        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);  
        
        //线程启动
        t1.start();
        t2.start();

        try {  
            //join用于使一个线程可以等待另一个线程执行完毕后再继续执行,这里使用它是为了产生可能的异常来表明线程在等待期间被中断。
            t1.join();
            t2.join();
        } catch (InterruptedException e) {  
            //printsStackTrace的问题又是一个巨坑,之后开专篇解答
            e.printStackTrace();
        }

        System.out.println("Final count: " + counter.getCount());
    }
}

2.4 ReadWriteLock接口

  • 提供了一种更细粒度的锁机制,适用读多写少的场景
  • ReadWriteLock接口有两个锁:一个读锁和一个写锁读锁是共享的,写锁是排他的;
  • 这也是操作系统的知识,具体原理需要去翻书,但是简而言之是读写锁的使用,有效解释了同步的合理情况只有“读读、先读后写、先写后读”三种情况;
  • ReadWriteLock 接口包含以下方法:
    1. Lock readLock(): 返回一个读锁。
    2. Lock writeLock(): 返回一个写锁。
  • Java 提供了一个ReadWriteLock接口的标准实现类ReentrantReadWriteLock。这是一个可重入的读写锁实现允许线程持有多个读锁,或一个写锁这里是指默认的情况,正常是有读锁后不能有写锁。但在某些实现中,如果线程已经持有读锁,则可以获得写锁,这种情况我依旧会开新的篇章叙述)。

如何使用读写锁ReadWriteLock

  1. 定义锁,使用ReentrantReadWriteLock类;
  2. writeLock.lock()获取写锁,执行写操作,在finallywriteLock.unlock()释放写锁;
  3. readLock.lock()获取读锁,执行读操作,在finallyreadLock.unlock()释放读锁。允许多个读线程同时执行,但在有写线程执行的时候,读操作将被阻塞
  4. 在线程内异常之前使用带有读写锁的读写相关操作;
  5. 使用join()方法平均各线程情况,等待所有线程完成

一个实例:

import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.concurrent.locks.Lock;

public class ReadWriteLockExample {
    private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
    private final Lock readLock = rwLock.readLock();
    private final Lock writeLock = rwLock.writeLock();
    private int value;

    // 写操作
    public void setValue(int value) {
        writeLock.lock();//包含写锁的写操作
        try {
            this.value = value;
            System.out.println(Thread.currentThread().getName() + " wrote " + value);
        } finally {
            writeLock.unlock();
        }
    }

    // 读操作
    public int getValue() {
        readLock.lock();//包含读锁的读操作
        try {
            System.out.println(Thread.currentThread().getName() + " read " + value);
            return value;
        } finally {
            readLock.unlock();
        }
    }
    //Java的方法非常鸡贼的避开了C++中关于读写操作的复杂实现的内容。
    public static void main(String[] args) {
        ReadWriteLockExample example = new ReadWriteLockExample();

        // 创建写线程
        Thread writer = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                example.setValue(i);//写操作,继承写锁性质
                try {
                    Thread.sleep(100); // 模拟写操作的时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Writer");

        // 创建读线程
        Thread reader1 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                example.getValue();//读操作,继承读锁性质
                try {
                    Thread.sleep(50); // 模拟读操作的时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Reader1");

        Thread reader2 = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                example.getValue();//同上
                try {
                    Thread.sleep(50); // 模拟读操作的时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "Reader2");
        //启动进程
        writer.start();
        reader1.start();
        reader2.start();
        //等待所有线程完成
        try {
            writer.join();
            reader1.join();
            reader2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();//出现中断,抛出异常,这个方法又是个坑,另外叙述。
        }
    }
}

总结

  • 显式锁(如ReentrantLock)提供了比synchronized关键字更灵活的同步机制,包括可中断的锁获取、尝试获取锁和带超时的锁获取。ReadWriteLock则提供了更细粒度的锁机制,适用于读多写少的场景。通过这些工具,可以编写出更高效和灵活的并发代码。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值