Java --- JUC之多线程

本文详细介绍了Java并发编程中的锁机制,包括悲观锁与乐观锁的概念及其应用场景。悲观锁在读写操作中倾向于加锁保证数据一致性,而乐观锁则在更新时检查数据是否被修改。synchronized关键字作为悲观锁的一种实现,用于方法和代码块的同步。此外,文章还讨论了公平锁与非公平锁的区别,以及可重入锁(递归锁)的重要性,特别是ReentrantLock和synchronized的可重入特性。最后,解释了synchronized重入的实现原理。

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

目录

一、悲观锁

 二、乐观锁

三、synchronized关键字

四、公平锁与非公平锁

五、可重入锁(递归锁)

5.1、synchronized的重入的实现机理


一、悲观锁

自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。使用synchronized关键字和Lock的实现类都是悲观锁。

适合写操作多的场景,先加锁可以保证写操作时数据正确。显示的锁定之后再操作同步资源。

 二、乐观锁

认为自己在使用数据时不会有别的线程修改数据或资源,所以不会添加锁。

在Java中通过使用无锁编程来实现,只是在更新数据的时候去判断,之前有没有别的线程更新了这个数据。

如果这个数据没有被更新,当前线程将自己修改的数据成功写入。

如果这个数据已经被其它线程更新,则根据不同的实现方式执行不同的操作,比如放弃修改、重新枪锁等。

判断规则:1、版本号机制Version 2、最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。

适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。乐观锁则直接去操作同步资源,是一种无锁算法。

三、synchronized关键字

1、作用于实例方法,当前实例加锁,进入同步代码前要获得当前实例的锁。

2、作用于代码块,对括号里配置的对象加锁。

3、作用于静态方法,当前类加锁,进去同步代码前要获得当前类对象的锁。

四、公平锁与非公平锁

公平锁:是指多个线程按照申请锁的顺序来获取锁,类似排队买票,先来的人先买后来的人在队尾排着,这是公平的  ReentrantLock lock = new ReentrantLock(true);//这里的true表示公平锁

非公平锁:指多个线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁,在高并发环境下,有可能造成优先级翻转或者饥饿的状态(某个线程一直得不到锁)

ReentrantLock lock = new ReentrantLock();默认就是非公平锁。

为什么有公平锁与非公平锁的设计,为什么默认使用非公平锁?

1、恢复挂起的线程到真正锁的获取还是有时间差的,从开发人员来看这个时间微乎其微,但从CPU的角度来看,这个时间差存在的还是很明显的。所以非公平锁能更充分的利用CPU的时间片,尽量减少CPU空闲状态时间。

2、使用多线程很重要的考量点是线程切换的开销,当采用非公平锁时,当一个线程请求锁获取同步状态,然后释放同步状态,所以刚释放锁的线程在此刻再次获取同步状态的概率就变得非常大,所以就减少了线程的开销。

什么时候用公平?什么时候用非公平?

为了更高的吞吐量,显然非公平锁是比较合适的,因为节省很多线程切换时间,吞吐量自然就上去了,否则就使用公平锁,大家公平使用。

public class SaleTicket {


        public static void main(String[] args) {
            //创建对象
            LTicket lTicket = new LTicket();
            //创建线程
            new Thread(()->{
                for (int i = 0; i < 40; i++) {
                    lTicket.sale();
                }
            },"aa").start();
            //创建线程
            new Thread(()->{
                for (int i = 0; i < 40; i++) {
                    lTicket.sale();
                }
            },"bb").start();
            //创建线程
            new Thread(()->{
                for (int i = 0; i < 40; i++) {
                    lTicket.sale();
                }
            },"cc").start();
        }
    }

    /**
     * 创建资源类
     */
class LTicket{
        //票数
        private int number = 30;
        ReentrantLock lock = new ReentrantLock(true);
        //卖票
        public  void sale(){
            try {
                //上锁
                lock.lock();
                //判断是否有票
                if(number > 0 ){
                    System.out.println(Thread.currentThread().getName()+"卖出:"+(number--) +"剩下:"+number);
                }
            } finally { //不管是否有异常都会执行解锁
                //解锁
                lock.unlock();
            }
        }
}

五、可重入锁(递归锁)

指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(锁对象是同一个对象),不会因为之前已经获取过还没有释放而阻塞。

在Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度上避免死锁。

5.1、synchronized的重入的实现机理

每个锁对象拥有一个锁计数器和一个指向持有该锁的线程的指针。

当执行monitorenter时,如果目标锁对象的计数器为零,那么说明它没有被其他线程持有,Java虚拟机会将该锁对象的持有线程设置为当前线程,并且将计数器加1。

在目标锁对象的计数器不为零的情况下,如果锁对象的持有线程是当前线程,那么Java虚拟机可以将其计数器加1,否则需要等待,直至持有线程释放该锁。

当执行monitorexit时,Java虚拟机则需将锁对象的计数器减1,计算器为零代表锁已被释放。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

鸭鸭老板

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值