来说一说你对锁都怎么分类?

在这里插入图片描述

悲观锁和乐观锁

我觉得悲观锁和乐观锁更多的是指一种思想,实现锁的不同方式。数据库、git都有不同的对应悲观锁和乐观锁思想的应用.

  • 悲观锁一般是互斥锁,
    但是1.阻塞和唤醒会带来性能损耗,要去切换用户态切换状态,查看要唤醒的线程等等;2.而且可能导致永久阻塞,比如发生了无限循环,死锁,那么阻塞等待获取锁的线程就可能永久等待了。3.还可能导致线程优先级混乱。适合并发多竞争很激烈的情况,代码复杂 或者循环量大

    java中最常见的悲观锁就是synchronized和lock类,但是注意,synchronized引入了偏向锁、轻量级锁等优化措施,readWriteLock在读的时候是共享锁,写的时候是独占锁。总体上来说还是得先拿到锁,才能执行

  • 乐观锁是非互斥锁,
    认为自己在操作的时候不会有其他线程干扰,所以不会锁住操作对象。更新的时候去再去比较对象数据是否被修改过,如果没修改过最好,如果发生了修改,就要选择放弃,抛弃,重试等策略。适合并发写入少,读取多的情况,提高读取的性能

    乐观锁基本都是基于CAS算法实现的,注意ABA问题,可以加版本号解决。有原子类,并发容器

共享锁和独占锁

独占锁 ,又称排它锁、独享锁
共享锁,又称读锁,获取到锁后可以查看但不能修改删除。为什么要这样设计呢 ?是因为很多线程读并不会造成线程安全问题,所以如果允许多个线程来读,就可以提高性能。

ReentrantReadWriteLock读写锁,其中读锁是共享锁,写锁时独占锁。
要么是多读,要么是一写。更具体点说就是,多个线程读没问题;以如果已经有线程在读,那么其他线程申请写锁则必须要等待释放读锁;如果一个线程已经在写,那么其他线程申请读或写都必须要等待释放写锁。

ReentrantReadWriteLock公平锁:不允许任何插队,不管写锁还是读锁,只要队列里已经有线程了就应该阻塞等待
ReentrantReadWriteLock非公平:

  • 写锁可以随时插队,即不需要阻塞
  • 读锁仅在等待队列中头结点不是写线程时可以插队:
    举个例子:假设线程2和线程4正在同时读,线程3想要写入,所以进入了等待队列且在头结点。然后线程5过来想要读,那么此时允不允许线程5去同时读呢?
    如果允许线程5插队读,则可能不停的有线程来插队读,写的线程就可能饥饿,所以ReentrantReadWriteLock不允许插队读,如果有写线程在排队则必须进入队列排队

ReentrantReadWriteLock支持写锁降级为读锁,但不支持读锁升级为写锁(避免死锁)。
为什么需要锁降级?比如一个任务刚开始需要写锁拿到某个日志文件,但是后续都只需要读就行,显然如果还是一直持有写锁性能就会差很多,如果可以降级为读锁,就能允许其他线程一起读,性能就会好很多。
为什么不支持锁升级为写锁呢?可能造成死锁,比如两个线程同时准备升级为写锁

公平锁和非公平锁

公平锁指的是完全按照线程请求的顺序来分配锁;非公平是不完全按照线程请求顺序,注意不是完全随机的,在一定情况下可以插队。

为什么要设计非公平锁呢?
非公平可以避免去唤醒线程时的空档期,提高使用性能,提高吞吐量。但有可能造成线程饥饿,即某些线程一直都拿不到锁

synchronize是非公平锁,ReentrantLock默认是非公平锁,但也可以构造公平锁。其实对应源码的实现很简单,就是在获取锁时是否放入队列

	//非公平锁 的尝试获取
	final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
        
	//公平锁的 尝试获取
	protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
            	//唯一的区别就在于这里,如果锁没被任何一个线程拿到,不像上面直接去cas争抢,
            	//而是会hasQueuedPredecessors去判断是否有其他线程等待的时间更长
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

注意tryLock是个特例sync.nonfairTryAcquire(1);,即使设置的公平锁,也会非公平的去争抢锁。

可重入锁和不可重入锁

什么是可重入锁呢?简单来说,就是一个线程可以多次拿到一个锁,Reentrant和synchronize都是可重入锁
有什么好处?
避免死锁,如果不是可重入锁,你拿到锁了,然后你想进入锁的另一个方法,你拿不到了!可能造成死锁
避免了重复的加锁和解锁
在这里插入图片描述

自旋锁和阻塞锁

也就是准备获取锁的线程无法获取到锁时,就先自旋,不用阻塞,避免了线程切换带来的开销。但是可能带来CPU的浪费,因此有自适应自旋锁。
synchronize引入的轻量级锁就是自旋锁的最好应用。适合少量线程竞争,且每个线程持有锁的时间不长的情况

可中断锁和不可中断锁

如果某个线程获取到了锁正在执行,线程B正在等待获取锁,可是由于等待时间过长,我们可以中断线程B,这就是可中断锁。
synchronize就是不可中断锁,lock是可中断锁,try( time)和lockInterruptibly都能响应中断。

锁优化

  • JVM提供了锁粗化和锁消除来优化锁
  • 我们在并发编程时要注意,尽量缩小同步代码块的范围,尽量不要锁住方法,减少加锁的次数。锁中不要再包含锁,容易造成死锁。选择合适的锁和工具类
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值