JavaWeb语法五:锁策略以及CAS

目录

 

1.常见的锁策略

2:Synchronized原理

2.1:加锁工作工程

2.2:其他的优化操作

2.2.1:锁消除

2.2.2:锁粗化

3.CAS

3.1:实现原子类

3.2:CAS中的ABA问题

3.2.1:什么是ABA问题

3.2.2:存在的BUG问题。

3.2.3:解决办法


1.常见的锁策略

1.乐观锁vs悲观锁

乐观锁:预期锁冲突的概率很低。乐观锁认为别的线程不会同时修改数据,所以不会上锁。

悲观锁:预期锁冲突的概率很高。每次去拿数据的时候,认为别的线程也会同时修改数据,所以每次拿数据的时候都会上锁。

 

2.读写锁vs互斥锁

读写锁:

加读锁:如果代码只进行读这个操作,就加读锁。

加写锁:如果代码只进行修改这个操作,就加写锁。

解锁:

针对读锁和读锁之间,是不存在互斥关系,读锁和写锁,写锁和写锁才存在互斥关系。

读写锁特别适合于"频繁读,不频繁写"的场景中。

互斥锁

互斥锁只有加锁和解锁这两个步骤,只要两个线程对同一个对象加锁,就会产生互斥。

 

3.重量级锁vs轻量级锁

重量级锁:做了更多的事情,开销也很大。加锁机制重度依赖了OS提供了mutex。通常情况下,悲观锁一般是重量级锁。

轻量级锁:做了较少的事情,开销也比较小。加锁机制尽可能不适用mutex,而是尽量在用户态代码完成,如果实在搞不定,再使用mutex。

 

4.挂起等待锁vs自旋锁

挂起等待锁:当莫个线程没有申请到锁的时候,此时该线程会被挂起来,即加入到等待队列中等待,当锁释放的时候,就会被唤醒,重新竞争锁。往往通过内核的一些机制来实现的。这是重量级锁的一种体现。

自旋锁:当莫个线程没有申请到锁的时候,该线程不会被挂起来,原地转圈圈,每隔一段时间来检测锁释放被释放,如果锁被释放了,那就竞争锁;如果没有释放,过一会儿再来检测。一旦锁被其他线程释放,就能第一时间获取到锁。

自旋锁是一种典型的轻量级锁的实现方式。

优点:没有放弃cpu,不涉及线程阻塞和调度,一旦锁被释放,就能第一时间获取到锁。

缺点:如果锁被其他线程持有的时间比较久,那么就会持续的消耗cpu资源,(而挂起来等待的时候是不消耗cpu的).

 

5.公平锁vs非公平锁

公平锁:是遵循先来后到(多个线程在等待一把锁的时候)。

非公平锁:是不遵循先来后到(多个线程在等候一把锁的时候,获取到锁的概率是均等的。)

 

6.可重入锁vs不可重入锁

针对同一个锁对象连续锁两次,不会产生死锁,那就是可重入锁,会产生死锁,那就是不可重入锁。

2:Synchronized原理

1.是一个乐观锁,也是一个悲观锁(根据锁竞争的激烈程度,自适应)。

2.不是读写锁,只是一个普通互斥锁。

3.是一个轻量级锁,也是一个重量级锁(根据锁竞争的激烈程度,自适应.)

4.轻量级锁的部分属于自旋锁来实现,重量级的部分基于挂起等待锁来实现。

5.非公平锁。

6.可重入锁。

2.1:加锁工作工程

b8dfba3eef5148a3a42513834075cd23.png

1)偏向锁: 

首个线程加锁,就会进入偏向锁状态。偏向锁不是真的“加锁”,只是做了一个标记。偏向锁本质上相当于“延迟加锁”,能不加锁就不加锁,尽量来避免不必要的加锁开销。但是该做的标记还是的做,否则无法区分何时需要真正的加锁。

这样的好处:就是后续如果没有其他线程竞争的时候,就不加锁,避免加锁带来的解锁。

偏向锁:主要用来优化同一个线程多次申请同一个锁的竞争

举一个例子:

我是一个渣男,看见了一个漂亮的女孩,想让她做我女朋友,但不想最后我不和她好的时候,她不跟我分手(跟这个女孩解锁),所以我决定搞暧昧,我和她假装是男女朋友,但实则我并没有给这个女孩表白,但一旦有其他男孩追这个女孩,我就立马表白,给这个女孩进行加锁。

2.2:其他的优化操作

2.2.1:锁消除

在单线程的时候,就不用使用锁,利用你使用了StringBuffeer Vector这个在标准库中进行了加锁,这时候编译器+JVM会自己判定锁是否可以给消除,如果可以,就直接消除。

2.2.2:锁粗化

这里的粗化,加锁的范围扩大。

锁的粒度越细,多个线程之间的并发度越高,加锁和解锁的开销也就越大。

public static void main(String[] args) {
        Object lock=new Object();
        Thread t=new Thread(()->{
            for (int i=0;i<10;i++){
                synchronized (lock){//锁细化
                    System.out.println(i);
                }
            }
        });
        Thread t1=new Thread(()->{
            synchronized (lock){//锁粗化
                for (int i=0;i<10;i++){
                    System.out.println(i);
                }
            }
        });
        t.start();
        t1.start();
    }

3.CAS

CAS:全称:Compare and swap。意思就是比较并交换。

我们假设线程中cpu中的数据为V,内存中的旧数据为P,要修改的数据为B。

1.要比较P 和V 是否相等。(比较)

2.如果相等,就将B写入到P里面(交换)

3.返回操作是否成功。

3.1:实现原子类

public static void main(String[] args) throws InterruptedException {        
    AtomicInteger num=new AtomicInteger(0);                                 
    Thread t=new Thread(()->{                                               
        for (int i = 0; i <5000 ; i++) {                                    
                  num.getAndIncrement(); //num++;                           
        }                                                                   
    });                                                                     
    Thread t1=new Thread(()->{                                              
        for (int i = 0; i <5000 ; i++) {                                    
             num.getAndIncrement();                                         
        }                                                                   
    }) ;                                                                    
    t.start();                                                              
    t1.start();                                                             
    t.join();                                                               
    t1.join();                                                              
    System.out.println(num.get()); //这个方法可以得到原子类内部的数值                       
}                                                                           
                                                                            
                                                                            

 

212f7f0f894c4643834c0dc3a6a6965e.png

 


3.2:CAS中的ABA问题

3.2.1:什么是ABA问题

假设存在两个线程 和 t1,有一个共享变量 num。初始值为10

线程 想使用CAS把num的值改为20。但是在这期间。t1 线程把num的值改成15,之后又改成10。

那么这时候线程 是否要更新num的值为20(线程 的num的值是否和内存num的值相等)

3.2.2:存在的BUG问题。

假如当你要向别人付款50元,你只有100元,由于网卡,你第一次点击确认没有反应,你又点击了一次。这时候生成了两个线程,当线程一完成付款的时候,你剩余50元,线程二在阻塞等待,这时候,你的好伙伴给你转账50元。你余额100。当线程二,拿自己线程中的值和内存的值进行比较相等,又扣除50。这样你就扣除了两次。

3.2.3:解决办法

给要修改的值,引入版本号。在CAS比较数据当前值和旧值的同时,也要比较版本号是否符合预期。

1.CAS操作在读取旧值的同时,也要读取版本号。

2.如果当前的版本号和读取的版本号相同,则修改数据,并将版本号加一。

3.如果当 前的版本号要低于读取的版本号,则操作失败。

88f5c5a73ad449d5b519188bd630255f.png

线程二最后读取的value值相同,但版本号不同。所以线程二操作失败。 

总结:

以上就是我总结的锁的相关知识和CAS的相关问题,若有错误之处,请留言纠错,若感觉不错。请一键三连。

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值