常见的锁策略

乐观锁/悲观锁:

一、乐观锁:它认为一般情况下不会发生并发冲突,所以只有在进行数据更新的时候,才会检测并发冲突,如果没有冲突则执行修改,如果有冲突则返回失败
二、CAS机制
全称Compare And Swap,字面意思:”比较并交换“,一个 CAS 涉及到以下操作:
我们假设内存中的原数据V,旧的预期值A,需要修改的新值B。

  1. 比较 A 与 V 是否相等。(比较)
  2. 如果比较相等,将 B 写入 V。(交换)
  3. 返回操作是否成功。
  • 问:CAS底层实现原理?
  • 答:Java层面CAS的实现是Usafe类
  • 在这里插入图片描述

Usafe类调用了c++的本地方法,
在这里插入图片描述

通过调用操作系统的Atomic::cmpxchg(原子指令)来实现CAS操作的

  • CAS的缺点:
    (1) ABA问题 -> AtomicInteger是存在ABA问题的
    A:预期旧值
    B:表示的新值
    private static AtomicReference money =
    new AtomicReference(100);
/**
 *ABA问题演示
 */
public class ThreadDemo93 {
    private static AtomicReference money =
            new AtomicReference(100);
    public static void main(String[] args) throws InterruptedException {

        //转账线程1  -100
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean result = money.compareAndSet(100,0);
                System.out.println("第一次转账(-100):"+result);
            }
        });
        t1.start();

        t1.join();

        //转入100
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                //+100
                boolean result = money.compareAndSet(0,100);
                System.out.println("转入100:" + result);
            }
        });
        t3.start();

        t3.join();

        //转账线程2  -100
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                //转账
                boolean result = money.compareAndSet(100,0);
                System.out.println("第二次转账(-100)"+result);
            }
        });
        t2.start();

    }
}

ABA问题统一的解决方案:增加版本号,每次修改之后更新版本号
private static AtomicStampedReference money =
new AtomicStampedReference(100,1);

/**
 *ABA问题演示
 */
public class ThreadDemo94 {
    private static AtomicStampedReference money =
            new AtomicStampedReference(100,1);
    public static void main(String[] args) throws InterruptedException {

        //转账线程1  -100
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                boolean result = money.compareAndSet(100,0,
                        1,2);
                System.out.println("第一次转账(-100):"+result);
            }
        });
        t1.start();

        t1.join();

        //转入100
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                //+100
                boolean result = money.compareAndSet(0,100,
                        2,3);
                System.out.println("转入100:" + result);
            }
        });
        t3.start();

        t3.join();

        //转账线程2  -100
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                //转账
                boolean result = money.compareAndSet(100,0,
                        1,2);
                System.out.println("第二次转账(-100)"+result);
            }
        });
        t2.start();

    }
}

(2)Integer高速缓存:-128——127

private static AtomicStampedReference money =
            new AtomicStampedReference(1000,1);

在这里插入图片描述

解决方案:调整Integer的高速缓存的边界值
(-Djava.lang.Integer.IntegerCache.high=边界值)
二、悲观锁(synchronized就是一种悲观锁):他认为通常情况下会出现并发冲突,所以他在一开始就会加锁

共享锁/非共享锁:

  • 一、共享锁:一把锁可以被多个线程拥有,这就叫共享锁
  • 二、非共享锁(独占锁):一把锁只能被一个线程拥有,这就叫非共享锁。(synchronized)

读写锁

就是将一把锁分为两个,一个用于读数据的锁(也叫做读锁),另一把锁叫做写锁,读锁是可以被多个线程同时拥有,而写锁则只能被一个线程拥有。

  • 读写锁中的读锁就是共享锁。
  • 读写锁的具体实现:ReentrantReadWriteLock
  • 读写锁的优势:锁的粒度更加的小,性能也更高。
  • 注意事项:读写锁中的读锁和写锁是互斥的。(防止同时读写锁产成的脏数据)

公平锁/非公平锁:

  • 一、公平锁:锁的获取顺序必须和线程方法的先后顺序保持一致,就叫公平锁
  • 二、非公平锁:锁的获取顺序和线程获取锁的前后顺序无关,就叫非公平锁。(默认锁策略)
  • 非公平锁优点:性能比较高
  • 公平锁优点:执行是有序的,所以结果也是可以预期的。
  • 公平锁:new ReetrantLock(true)
  • 非公平锁:new ReeatrantLock()/ new ReeatrantLock(flase)/ synchronized

自旋锁

通过死循环一直尝试获取锁

while(true){
if(尝试获取资源){
return;
}
}

自旋锁的缺点:
缺点其实非常明显,就是如果之前的假设(锁很快会被释放)没有满足,则线程其实是光在消耗 CPU 资源,长期在做无用功的

可重入锁

当一个线程获取一个锁之后,可以重复的进入。即允许同一个线程多次获取同一把锁(比如synchronized)

 //创建锁
    private static Object lock = new Object();

    public static void main(String[] args) {
        //第一次进入锁
        synchronized (lock){
            System.out.println("第一次进入锁");
            synchronized (lock){
                System.out.println("第二次进入锁");
            }

        }
    }

在这里插入图片描述

问题1: 你是怎么理解乐观锁和悲观锁的,具体怎么实现呢?

1.乐观锁 -.> CAS -> Atomic*来实现 , CAS是由 V(内存值),A(预期旧值),B(新值)组成,然后执行的时候是使用V==A对比,如果结果为true则表明没有并发冲突,则可以直接修改,否则不能修改。CAS是通过调用C++实现提供的UnSafe类中的本地方法(ComPareAndSwapXXX)来实现的,C++是通过调用操作系统Atomic::cmpxchg(原子指令)来实现的
2.悲观锁 -> synchronized 在Java中是将锁的ID放到对象头来实现的。synchronized 在JVM层面是通过监视器锁来实现,synchronized在操作系统中是通过mutex(互斥锁)来实现的

问题2:什么是偏向锁?

答:将自己偏向锁的信息记录在对象头里面,第一个线程第一次访问时,将线程ID存储在对象头中的偏向锁标识中,每次等线程来临时,从对象头里面的偏向锁标识取出来,和线程ID做对比,如果相等就可以开始执行代码,否则不能执行

问题3:sunchronized锁优化(通过锁消除来实现的)?

答: JDK1.6锁升级的过程:
(1)无锁 (无人去访问的时候)
(2)偏向锁 (第一个线程第一次访问),将线程ID存储在对象头中的偏向锁标识
(3)轻量级锁(如果没有得到锁)(自旋)
(4)重量级锁(实现用户内存到系统内存之间的切换)性能比较低

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值