java并发编程---锁介绍(乐观锁/悲观锁、独占锁/共享锁、公平锁/非公平锁、互斥锁/读写锁、可重入锁、不可重入锁、自旋锁、分段锁)

目录

乐观锁

悲观锁

独占锁

共享锁

公平锁

非公平锁

互斥锁

读写锁

不可重入锁

可重入锁

自旋锁

分段锁


睡觉前,突然有感而发,这世界就是一个unfairsync,而不是fairsync,于是想到了锁,总结一下部分锁的概念,具体的使用本人也在不断学习中,后期学习ok也会和大家分享

乐观锁

乐观锁,可能看名字不知道什么意思,说简单点就是,乐观锁,乐观的认为,自己需要的数据,不会被别人更改,但是为了保证安全,在更新的时候,还是会判断一个这个期间有没有别的线程更新这个数据,通俗点说就是,比较版本是否一致,最常用的:CAS机制

常用于读的场景

悲观锁

悲观锁,如同字面意思一样,总是向坏的方面去考虑,举个例子,有个人要停车,他总觉得车位别人会跟他抢,于是在使用的时候,他都会把车位锁上,这样别人就进不去,也就是所谓的阻塞

所以放在我们的开发中就是,悲观锁认为,每次它要拿的数据,都会被别的线程修改,所以它在拿数据的时候,就会上锁,在上锁期间,只有它自己可以使用访问,别的线程都会阻塞,最常用的:synchronize

常用于写的场景,读的效率低于乐观锁,但是在写的场景,先加锁可以保证数据的准确性

独占锁

就是说这个锁一次只能被一个对象持有

共享锁

指的是这个锁可以被多个线程同时持有使用

公平锁

见名知意,是一个公平的锁,好吧这是个废话

举个例子,有一个资源,n个线程同时访问,但是这个资源只支持一个线程去使用,当有一个线程在使用这个资源,别的线程就会排队,进入阻塞状态,当上一个线程使用完毕,就会按照排队顺序唤醒下一个等待的线程,说白了就是,先来后到,先到先得

优点:不会出现线程饿死的情况

缺点:整体的吞吐情况和效率相对于非公平锁比较低,且CPU要挨个唤醒所有被阻塞的线程,开销比较大

可以通过以下方式来获得公平锁

ReentrantLock fairsync = new ReentrantLock(true);

非公平锁

与公平锁对立,非公平锁,就是说不管先来后到,谁抢到谁牛逼,谁就用,就跟这个世界一样

举个例子,还是一个资源,n个线程,线程名从0到n,0线程抢到了资源的使用权,于是1线程到n线程全部阻塞,然后0线程使用完毕,这个时候,注意!0线程会继续参与到这个资源使用权的抢夺之中,而不会去排队,如果抢不到,再使用公平锁的方式,去排队,如果抢到了,就使用

优点:可以减少CPU唤醒线程的开销,整体吞吐量较高,因为线程有几率不阻塞就直接获得锁对资源进行使用

缺点:可能会出现线程饿死的情况,也可能某个线程很久很久很久才能获得锁

可以通过以下方式来获得锁,顺便一说,本人好奇看了看 ArrayBlockingQueue 和 LinkedBlockingDeque 的锁结构,发现默认都是非公平锁,且,连 ReentrantLock 默认的都是非公平锁

ReentrantLock unfairsync = new ReentrantLock(false);

互斥锁

在访问共享资源之前进行加锁,访问完成之后对共享资源进行解锁,对共享资源加锁之后,任何尝试对共享资源加锁的线程都会被阻塞,除非当前线程解锁,永远只有一个线程能够访问共享资源

读写锁

个人觉得,单独说读写锁不合适,应该把它和独占锁共享锁放一起更好理解,六个字,写独占,读共享,这也就有了两个特性,当你对这个读写锁添加读锁的,时候,你再添加写锁,就无法添加,会被阻塞,但是当你对这个读写锁添加写锁的时候,无论你添加读锁还是写锁,都会被阻塞

因此,读写锁更适合于多度少写的场景下,一定要注意,读写锁是一把锁,不是两把,别误会大家.....

不可重入锁

这个锁可能比较绕,举个例子

A方法调用了B方法,A方法被n个线程调用,A方法使用前加了锁,B方法本身也需要加锁,于是就出现了,A加锁,B也要加锁,两个人用的同一个锁对象,就出现了死锁,A方法要执行完需要B方法执行完释放锁,但是B方法执行完又需要A方法释放锁,这就是不可重入锁

可重入锁

可重入锁,就是为了解决上面的问题,其实实现可重入锁也很简单,核心就是加锁方,所谓解铃还须系铃人,锁也是,谁上的锁,肯定谁解锁

由于本人没有翻到可重入锁的源码,所以只能让本菜鸡来尝试实现一下这个锁

package own.study.auto;

public class MyLock {

    boolean isLock = false;
    int lockNum = 0;
    Thread lockThread = null;

    public synchronized void lock() throws InterruptedException {
        // 判断是否已经上锁,如果已经上锁,并且不是当前调用 lock 申请上锁的线程,那就自旋
        while (isLock && this.lockThread != Thread.currentThread()) {
            this.wait();
        }
        // 没有走上面的自旋,说明没上锁,或者依旧是当前线程申请进行加锁,可以让进行二次加锁,记录是谁加的锁,加锁了几次
        lockNum++;
        isLock = true;
        lockThread = Thread.currentThread();
    }

    public synchronized void unLock() {
        // 如果加锁了,并且是当前线程加的锁,就进行加锁次数减一
        if (isLock && this.lockThread == Thread.currentThread()) {
            lockNum--;
            // 判断一下加锁次数在减一之后是不是0,如果是0,那说明所有的锁都释放了,可以将标志位置为未加锁状态,同时唤醒下一个线程
            if (lockNum == 0) {
                isLock = false;
                notify();
            }
        }
    }
}

自旋锁

这个听起来是不是很高大上,那我告诉你,其实自旋锁的原理很多开发者都用过,比如,你消费MQ,是不是一个死循环,无限去读

我没跑题,因为自旋锁其实就是一个死循环,一直检测状态,比较浪费性能老实说

分段锁

严格来说,个人觉得分段锁更像是一种锁的设计思想,而不是一种锁,拿ConcurrentHashMap来说,默认16,采用分度锁,其实就是每一段看做一个独立的个体,进行加锁,用的时候你只需要去找自己用的那一段的锁就OK

最后,大家晚安喽~ 晚上十一点啦,到点睡觉,祝大家做个好梦 OvO~  如果以上有说的不对的地方,请指正,多谢各位大佬们!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值