前言

本文主要介绍Java中的锁

一、Lock

  • synchronized效率低,不够灵活并且无法知道是否成功获取到锁

1.lock()

private static Lock lock = new ReentrantLock();

    public static void main(String[] args) {
        lock.lock();
        try {
            //获取本锁保护的资源
            System.out.println(Thread.currentThread().getName() + "开始执行任务");
        } finally {
            lock.unlock();
        }
    }

2.trylock(long time,TimeUnit unit)

当超时之后还没获取到锁,那么就退出

		try {
            if (lock1.tryLock(800, TimeUnit.MILLISECONDS)) {
                try {
                    System.out.println("线程1获取到锁1");
                    Thread.sleep(new Random().nextInt(1000));
                    if (lock2.tryLock(800, TimeUnit.MILLISECONDS)) {
                        try {
                            System.out.println("线程1获取到锁2");
                            System.out.println("线程1成功获取到两把锁");
                            break;
                        } finally {
                            lock2.unlock();
                        }
                    }else {
                        System.out.println("线程1获取锁2失败,已重试");
                    }
                } finally {
                    lock1.unlock();
                }
            } else {
                System.out.println("线程1获取锁1失败,已重试");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

3.LockInterruptibly

相当于tryLock 的超时时间设置为无穷,可以一直等待,并且可以被中断。

二、悲观锁与乐观锁

思想:
悲观锁:认为数据很容易会被外界修改,所以在处理数据时先加锁,并使数据一直处于锁定状态
乐观锁:认为数据一般没有冲突,因此不会加锁,但是当数据和我一开始拿的不一样了,就会选择放弃、报错、重试等策略

  • 悲观锁:适合并发写入多的情况,可以避免大量的无用自旋等消耗

  • 乐观锁:适合并发写入少,大部分都是读的情况,不加锁的能让读取性能大幅提高

三、可重入锁

public class GetHoldCount {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        //打印当前持有的锁的数量
        System.out.println(lock.getHoldCount());
        lock.lock();
        System.out.println(lock.getHoldCount());
        lock.lock();
        System.out.println(lock.getHoldCount());
        lock.lock();
        System.out.println(lock.getHoldCount());
        lock.unlock();
        System.out.println(lock.getHoldCount());
        lock.unlock();
        System.out.println(lock.getHoldCount());
        lock.unlock();
        System.out.println(lock.getHoldCount());
    }
}

结果

可重入锁源码剖析

在获取锁时先去判断是否已经占用了当前锁,若是,就把status+1。
当持有锁的数量等于0时,就设置为free

对于非可重入锁,由于它肯定没有持有锁,那么就直接去尝试获取锁
非可重入锁

四、公平锁和非公平锁

公平锁:按照线程请求的顺序来获取锁

非公平锁:例:线程A正持有锁,并且即将释放,这时候,线程B在队列中,当线程A释放锁,B要被唤醒,然后再去拿锁。然而,再A释放锁的同时,线程C来了,要拿锁。因为这时候C已经是醒的了,而B待唤醒,所以,在这个空挡期,线程C就可以直接去拿锁,这就显得“非公平”。

非公平的目的:提高效率

非公平的劣势:可能会产生饥饿

特例:tryLock()自带插队属性,不遵守设定的公平的规则,一旦有线程释放了锁,那么这个正在tryLock的线程就能获取到锁,即使在它之前已经有其他线程现在再等待队列里。

五、共享锁(读锁)和排他锁(写锁)

要么一个或多个线程同时有读锁,要么一个线程有写锁(要么多读,要么一写)


图片来自文章:轻松掌握java读写锁(ReentrantReadWriteLock)的实现原理

  • 由于在实际的情况中,读锁的比重是特别高的,因此,在非公平的情况下,可能会出现:读锁一直插队(读锁插队十分容易),写锁总是得不到运行,因此可以采用一定的策略:写锁可以直接插队,读锁仅在等待队列头结点不是想获取写锁的线程的时候可以插队(读锁可以插读锁的队)

锁的升降级:写锁可以降级,读锁不能升级
Why?
如果可以升级,考虑这样情况:AB两个线程都拿到了读锁,这时候,AB都想将锁升级,那么A希望B释放锁,B希望A释放锁,这时候他们俩都在等待对方,就进入了死锁。

六、自旋锁和阻塞锁

有时候,CPU调度的时间甚至超过代码执行的时间,因此,可以采取这样的策略:当线程拿不到锁时,让他继续占用CPU,自旋一下,看看持有锁的线程是否会很快释放锁,从而避免切换现场造成的开销。


七、总结:如何优化锁和提高并发性能

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值