锁策略,CAS和synchronized的锁优化

  1. 锁策略
  2. synchronized锁优化
  3. CAS锁优化

1.锁策略

乐观锁VS悲观锁

加锁的时候,预测多个线程访问同一个共享变量冲突的概率大还是小

预测当前锁冲突的概率大,就尝试加锁,就是要用悲观锁策略,需要加锁,消耗资源,效率降低

反着就是要用乐观锁,不加锁,在访问的时候才会检测是否会产生所冲突,如果产生并发冲突,就返回错误信息,让用户决定下一步怎么做;不产生锁冲突就不用加锁,效率提高;

synchronized既是悲观锁又是乐观锁

轻量级锁VS重量级锁

指的是加锁的工作的多还是少,加锁的工作多,开销大就是重量级锁,反着就是轻量级锁;乐观锁就是轻量级锁,悲观锁就是重量级锁,两者概念相近,有时混着用,只是出发点不一样;

所以synchronized即使轻量级锁又是重量级锁

自旋锁VS挂起等待锁

自旋锁是轻量级锁的一种典型实现方式,挂起等待锁是重量级锁的一种典型实现方式;

指的是等待线程获取锁的方式不一样,自旋锁是一个while循环一直去访问锁,知道该锁被释放就去尝试获取锁;挂起等待锁是指只要锁已经被其他线程获取,该线程就挂起等待,进入阻塞状态,直到其他线程释放锁,唤醒阻塞的线程,该线程再去尝试获取锁;

以上两种获取锁的方式不同,自旋锁在锁一释放就能就是获取锁,消耗很多CPU资源,但是获取锁的时间快;挂起等待锁,由于被唤醒的时机是不确定的,拿到锁慢,消耗CPU少;

synchronized的轻量级锁部分基于自旋锁实现的,由CAS机制操作;重量级锁部分基于挂起等待锁实现,由系统调用API实现;

公平锁VS非公平锁

指的是所有等待线程获取锁的机会公平与否,如果是按照先来回到的顺序,那么这个锁就是公平锁,如果等待的线程获取锁的概率是均等的,这个锁就是不公平锁,这是由以前发明锁的大佬定义的,如果另一个时间线定义的是是按照概率均等获取锁是公平的,就又是另一个说法;

synchronized是不公平锁,竞争同一个锁的线程获取锁的概率是均等的;

可重入锁VS不可重入锁

可重入锁就是一个线程,针对一个锁,连续加锁两次,不会死锁,会产生死锁就是不可重入锁;

synchronized是可重入锁,c++的stu::mutex是不可重入锁;

互斥锁VS读写锁

互斥锁是一个锁只能同时被一个线程占有,其他线程要获取要等待,synchronized就是典型的互斥锁;

读写锁是分为加读锁,加写锁,解锁,Java中的读写锁是:(1)读锁与读锁之间不会产生互斥;(2)写锁和写锁之间会产生互斥;(3)读锁和写锁之间会产生互斥;降低读锁和读锁的冲突,提高并发能力;

2.synchronized锁优化

锁升级

分为无锁--->偏向锁--->轻量级锁--->重量级锁

某个线程没有锁的是时候就是一个无锁的状态,当这个线程获取到synchnorized时会变成偏向锁,但是不会真正的加锁,只是这个锁会记录目前是哪个线程获取到这个锁,只是做一个标记(不会产生任何的开销,非常快);直到有其他线程要尝试去获取这个锁的时候,就会立即由偏向锁转化成轻量级锁,使得其他线程获取不了这个锁,后续可能会升级会重量级锁,并且这个synchronized只能升级,不能降级;

主打的就是一个“懒”,能不加锁就不加锁,能多慢加锁就多慢加锁,甚至于不加锁,就是下面要谈到的锁消除

锁消除(编译器的优化)

在编译器编译的过程中,会判断该线程是否需要这个锁,如果不需要就会优化掉,消除掉这个锁;举个例子,比如该程序中只有一个线程,就没有必要加锁,编译器就会把锁优化掉;

锁粗化(编译器的优化)

指的是锁的粒度,这个锁要完成的任务越多,代码量越多,粒度就越大;如果一个程序中有多个加锁的细粒度的代码块,编译器会根据情况把他们合并成一个粗粒度的代码;

举个例子就是:你妈妈一会让你出门买个盐,等你回来又让你出门倒个垃圾,等你回来又让你去酱油,上面代指的就是多个细粒度的代码块,而编译器就会把他们都放到一个代码块里面加锁,一次性完成,提高执行效率;

编译器的优化要保证优化前后逻辑要等价才能真正优化;

3.CAS

CAS(compare and swap)就是比较和交换,是一条CPU指令,原子性的指令,完成比较和交换;可以把它想象成一个方法,下面是伪代码:

//如果寄存器1的值与某内存地址的值相等,将寄存器2的值与内存的地址交换
        boolean cas(address,reg1,reg2){
            //更准确来说就是把寄存器2的赋值给内存,后续不关心寄存器2的值
            if(*address==reg1){
                *address=reg2;
                return true;
            }
            return false;
        }

由于CPU提供了上述指令,因此操作系统内核也能够完成上述操作,提供这样的CAS的api,JVM由于对系统的CAS的api进一步封装,在java中也能够使用CAS操作,实际上,被封装在一个“unsafe”的包中,所以不建议直接使用CAS,所以java中有一些类,对CAS进行封装,就是典型的“原子类”;

在java.util.concurrent.atomic中提供了很多原子类,其中AtomicInteger提供了整数++,--的原子性操作,下面示范一下

import java.util.concurrent.atomic.AtomicInteger;

public class demo3 {
    public static void main(String[] args) {
        AtomicInteger count=new AtomicInteger(0);
        //count++
        count.getAndIncrement();
        System.out.println(count);//1
        //++count
        count.incrementAndGet();
        System.out.println(count);//2
        //count--;
        count.getAndDecrement();
        System.out.println(count);//1
        //--count
        count.decrementAndGet();
        System.out.println(count);//0
        //count+=10;
        count.getAndAdd(10);
        System.out.println(count);//10
    }
}

既然是原子类,那么一定是线程安全,下面是多线程的示范

import java.util.concurrent.atomic.AtomicInteger;

public class demo4 {
    private static AtomicInteger count=new AtomicInteger(0);
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) {
                    count.incrementAndGet();
                }
            }
        };
        Thread t2=new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 50000; i++) {
                    count.incrementAndGet();
                }
            }
        };
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);//10_0000
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

a添砖Java

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值