2021-06-05# Java基础(dayFourteen):锁的两种方式

本文深入探讨了Java并发编程中的锁机制,包括synchronized关键字和ReentrantLock类。ReentrantLock提供了公平锁选项,允许线程按照等待时间顺序获取锁。同时,它支持条件对象,使得线程在不满足特定条件时能够等待并释放锁。synchronized修饰静态方法和同步块,确保了类和对象级别的互斥访问。文章通过实例展示了各种锁的使用和效果,揭示了它们在并发控制中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

并发同步

当两个或两个以上的线程需要共享对同一数据的存取或修改时,会发生覆盖现象,覆盖情况取决于线程调度器先调度哪一个线程,这种情况通常称为竞态条件

锁对象

Java提供两种机制防止并发访问代码块

  1. Synchronized关键字
  2. ReentrantLock类
ReentrantLock

使用ReentrantLock来进行加锁的话,都是要用在try语句上(即lock方法后面必须紧跟try语句),最后一定要用finally来释放锁
在这里插入图片描述
该结构确保了在任何时刻只有一个线程可以进入下面的lock后的语句块里面,也就是临界区

一旦一个线程锁定了锁对象,其他任何线程都无法通过lock语句,当其他线程调用lock时,则会进行暂停,直到前面的线程释放这个锁对象,也就是unlock

举个栗子

下面是某类加锁的方法
在这里插入图片描述
测试的main方法
在这里插入图片描述
结果为
在这里插入图片描述
可以看到在lock前面的代码是可以让其他线程执行的,但是lock后面的代码必须等待前面的线程释放锁才可以去执行,期间就会发生阻塞

这里要注意的是,每个对象会有自己的ReentrantLock,所以要争抢同一个ReentrantLock对象才会发生阻塞,该锁就可以保证串行化访问,如果是两个不同的对象,以上个栗子为例,就是两个不同的TestSyncFunny对象,那么两个线程得到的是不同的锁,是不会发生阻塞现象的

这个锁称为重入锁(ReentrantLock:重入),因为这个锁可以被线程反复获得,该锁会有一个持有计数来跟踪对lock方法的嵌套使用,即在加锁的try语句块里面调用方法A,该方法A加的是同一把锁(同一个ReentranLock),所以获得B的锁也会去影响A的运行

举个栗子

    private ReentrantLock lock = new ReentrantLock();
    public void doSyncTo(String name){
        System.out.println("下面"+name+"也要进行加锁");
        lock.lock();
        try{
            System.out.println(name+"已经加锁了~~~~");
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void doSync(String name){
        System.out.println(name+"还没进行上锁----");
        lock.lock();
        try{
            System.out.println(name + "上锁了,dododoodid");
            doSyncTo(name);
            System.out.println(name+"调用完了另一个加锁方法,并且已经释放那个锁了");
            Thread.sleep(2000);
            System.out.println(name+"睡醒了");
            //code....
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

上面是两个加锁的方法,用都是同一个ReentrantLock,而且在doSync方法中,还调用了doSyncTo的方法

在这里插入图片描述
可以看到,调用doSync拿到的ReentrantLock,会让其他线程也无法调用doSyncTo加锁后的那些方法,必须要等线程释放了最外的一层锁,即doSync的lock才可以调用,单独释放了doSyncTo里面的lock也是不够的

公平锁

我们也可以使用ReentrantLock来构建一个公平锁

调用下面的构造方法,传入true参数
在这里插入图片描述
公平锁是指:锁释放的时候,倾向于让等待时间最长的线程获取该锁

不过公平锁会影响性能,所以一般情况下都是非公平的,而且公平锁不一定是绝对公平的,还要取决于线程调度器,如果线程调度器忽略了一个已经为锁等待很长时间的线程,那就不能实现公平

条件对象

假设出现了某个线程进入了临界区后,需要等待其他线程完成某些操作才可以继续执行,那么如果这个线程拿到了ReentrantLock,那么其他线程拿不到ReentrantLock,不能进行执行命令,不可能完成指定操作,让拿到了锁的线程继续执行下去,那么这个情况该如何解决

对此的解决方法就是,让该线程现在暂停,并且放弃对锁的持有,让其他线程完成操作,当达到条件后,该线程才可以重新激活,从之前暂停的地方进行下面的操作

所以我们可以给其加上一个条件对象,当不满足条件的时候,让线程在这里进行等待并且释放锁,当满足条件时,而且锁可用的时候,该线程不会立马去争夺该锁从而变成可运行的状态,然后执行剩下的操作,而是当有另一个线程同样判断是否满足条件的时候,才会重新激活等待这个条件的所有线程(这些线程是没有办法自己激活自己的),也正因为这个,经常会导致死锁的现场,假设修改条件的线程发生了阻塞,而等待条件的线程进行了await,那么这两个线程都完蛋了

获取条件对象
在这里插入图片描述
条件对象是一个Condition,调用await方法就可以进入线程的等待集(等待条件满足被激活)中,当条件满足后,另一个进行同样判断的线程(也是进入了while(number < 9)的线程)就会去激活等待集里的所有线程,然后这些线程才能去抢锁,抢到锁才可以继续从暂停的地方继续执行下去

对于ReentrantLock的条件对象可以使用多个,从而形成不同的线程等待集

synchronized关键字

从java1.0版本开始,Java中的每个对象都有一个内部锁,也成为内部对象锁,使用synchronized关键字

使用该关键字就不用显示地去使用一个重入锁(ReentrantLock),而内部对象锁只能由一个关联条件,也就是只有一个条件对象(区别于ReentrantLock可以有多个条件对象)

对应条件对象相关的方法如下

  • await:当前线程进入等待集
  • notify:随机唤醒一个等待集
  • notifyAll:唤醒所有的等待集
静态同步方法

synchronized也是可以修饰静态方法的,表示当前线程获得相关类对象的内部锁(而不是对象的内部锁),获取了相关类对象的内部锁就没有其他线程可以调用这个类的任何synchronized修饰的静态方法(不仅仅是调用的,其他也会)

同步块

有时候,为了仅仅对方法里面的部分代码进行同步,可以使用同步块来获取当前对象的内部锁来进行锁定
在这里插入图片描述
我们可以规定代码块使用的是类的内部锁还是对象的内部锁

类的不同实例拥有相同的类的内部锁,但对象的内部锁却是不同的,这类的内部锁与对象的内部锁是互不干扰的,而且拿到实例的内部锁是不会影响静态方法的,而拿到类的内部锁也是不会影响非静态方法的

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值