【Java】lock锁

一、引入

我们刚刚学习完了同步代码块和同步方法,它们里面有个细节要注意。

这里以同步代码为例。我们发现,当有线程进入到同步代码块后,这里的锁是会自动关闭的。

当线程执行完里面的代码后,锁又会自动打开。

image-20240506194701253

也就是说这里的锁的开关,我们是没有办法自己控制,都是自动的。

但如果我想手动去加锁,或者手动释放锁,有没有办法呢?

当然这必须是有的,在JavaJDK5以后提供了一个新的锁对象Lock。

Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。

Lock中提供了获得锁、释放锁的方法,这样我们就可以去手动上锁、手动释放锁。

方法名说明
void lock()获得锁
void unlock()释放锁

Lock是接口不能直接实例化(创建对象),这里采用它的实现类ReentrantLock来实例化

在创建对象的时候直接用它的空参构造就行了

方法名说明
ReentrantLock()创建一个ReentrantLock的实例

二、引出问题

将之前的代码使用 Lock 来改写

public class MyThread extends Thread{
    static int ticket = 0;

    //Lock是一个接口,在创建的时候需要创建它实现类的对象
    Lock lock = new ReentrantLock();

    @Override
    public void run() {
        //1.循环
        while(true){
            //由于上锁跟释放锁跟同步代码块是重复的,因此需要将同步代码块给注释掉
            //2.同步代码块
            //synchronized (MyThread.class){
            lock.lock(); 

            //3.判断
            if(ticket == 100){
                break;
                //4.判断
            }else{
                try {
                    Thread.sleep(10);

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket++;
                System.out.println(getName() + "在卖第" + ticket + "张票!!!");
            }
            //  }
            lock.unlock(); // 释放锁
        }
    }
}

如果此时直接运行代码,票依旧是有重复的,并且也会超出范围。这是为什么呢?

由于我们用的是第一种方式实现的多线程,MyThread 需要创建很多次

image-20240506201655146

如果 MyThread 创建很多次,那么势必造成这里的 Lock锁 有多个对象,所以我们需要在它的前面加上 static静态关键字,这就表示所有的对象都共享同一把锁。

static Lock lock = new ReentrantLock();

运行后重复的票没有了,超出范围的票也没有了,但是新的问题又出现了:程序居然没停!

image-20240506200005501

假设三个线程都在 run() 这里抢夺CPU的执行权,一开始假设线程1抢夺到了CPU的执行权。

然后执行到while循环、lock.lock(),此时相当于将锁给锁上了,线程1进来。

进来后做了一个判断:ticket == 100,发现不等于,就会执行 else 中的代码块,于是就会睡觉。

睡觉的时候CPU的执行权就会被抢走,给线程2或者线程3,它抢走后,它执行到了 lock.lock(),此时它无法获取到锁对象了,锁已经关闭了,因为线程1已经事先拿到了锁,一旦拿到了锁之后,锁就会关掉。其他的线程就算你抢到了CPU的执行权,它也会被关在外面。到这里还是跟之前是一样的。

继续往下分析,当线程1醒来后,执行下面的代码,直到调用 unlock(),即释放锁。

此时回到循环的上面,继续和线程2线程3抢夺CPU执行权。

假设现在每次都是线程1抢到的,因此线程1会把所有的票都卖完。

假设现在是最后一次,ticket = 100。假设还是线程1抢到了执行权,此时通过 lock.lock() 拿到了锁,然后进行 ticket == 100 ,判断为true,执行break,直接跳到了循环的外面,此时问题就出现了,它都没有执行到这里的 unlock()

这就导致了线程1虽然结束了,但是它还拿着锁对象出去的,没有把锁给打开,这就导致了线程2和线程3一直停在 lock.lock(),此时程序就不会停止。

image-20240506200847457

三、解决问题

但是这并不是我想要的,我想要的是:不管是什么样的情况,程序都能停止,即 lock.unlock() 都能执行到。

哪怕你循环结束了,你也得把这个锁释放了。

有的同学会说:在break前面再加一个 unlock() 不就行了吗?

这确实是可以的,但是 unlock() 算是扫尾代码,像这种扫尾代码我干嘛要写两遍呢?

有没有一种更为简单的办法?是有的,而且是最稳妥的。

在Java中异常的处理体系中,有个 finally,特性:不管怎么样,它里面的代码一定会执行,因此我们可以利用这个特性将 unlock() 写在 finally 中。

改写:将从锁开始全部的代码,全都放到 try-catch 中。

public class MyThread extends Thread{

    static int ticket = 0;

    static Lock lock = new ReentrantLock();

    @Override
    public void run() {
        //1.循环
        while(true){
            //2.同步代码块
            //synchronized (MyThread.class){
            lock.lock(); //2 //3
            try {
                //3.判断
                if(ticket == 100){
                    break;
                    //4.判断
                }else{
                    Thread.sleep(10);
                    ticket++;
                    System.out.println(getName() + "在卖第" + ticket + "张票!!!");
                }
                //  }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值