Java 各种锁的理解与实现-1

2 篇文章 0 订阅
文章详细介绍了volatile关键字的作用,包括保证可见性和禁止重排序,并对比了synchronized的特性,强调其作为悲观锁的角色,保证了可见性和原子性。同时提到了CAS机制作为乐观锁的实现,以及其在Java并发包中的应用,指出了CAS的局限性如ABA问题和自旋开销。
摘要由CSDN通过智能技术生成

一.  volatile -线程间通信方式:

        保证可见性:

                1)写:当某个线程修改了某个共享变量的值,会立即将修改值从自己工作内存刷新回主内存,并立即通知其他线程

                2)读:所有线程读取该变量值时候,必须去主内存读取,不能读取cpu缓存

        保证有序性:内存屏障,禁止重排序

        只能修饰在变量上,使得 cpu 每次对于该变量的修改和读取都从内存中操作,而不是从CPU cache 中操作,保证共享变量对所有线程的可见性,但是并不能保证原子性

二. 悲观锁 --- synchronized :

        synchronized --- 非公平锁 和 可重入锁

        当某个线程访问被 synchronized 修饰的方法或代码块时,会先检查有没有其他线程在占用该对象锁,如果有,则该线程进入阻塞状态,直到占用该锁的线程使用完毕释放锁;synchronized 能够保证同一时刻只能有一个线程能访问,可修饰在类,方法,变量上,保证多线程下操作的可见性和原子性

1)对象锁 --- synchronized 加在非静态方法上:普通同步方法

         对象锁:普通同步方法使用同一个锁,对象锁,当某一个对象调用同步方法获得该锁时,其余的对象在访问该类同步方法会被阻塞,直到当前对象同步方法执行完成,释放出锁

 class  Phone {
    public synchronized void sendEmail(){
        try { TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}
        System.out.println("-----sendEmail");
    }
    public synchronized void sendSMS(){
        System.out.println("-----sendSMS");
    }
    public void sendHello(){
        System.out.println("-----sendHello");
    }
}

public class Lock1Demo{
    public static void main(String[] args) {
        Phone phone = new Phone();

        new Thread(()->{
            phone.sendEmail();
        },"t1").start();

        new Thread(()->{
            phone.sendSMS();
        },"t2").start();

        new Thread(()->{
            phone.sendHello();
        },"t3").start();
    }
 }

运行结果:

 2) 类锁 --- synchronized 加在静态方法上: 静态同步方法

        类锁:静态同步方法使用同一个锁,类锁,当某一个静态同步方法的对象调用者获得该锁时,其余对象的静态同步方法也会被阻塞,直到当前静态同步方法执行完成,释放出锁

 class  Phone {
    public static synchronized void sendEmail(){
        try { TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}
        System.out.println("-----sendEmail");
    }
    public static synchronized void sendSMS(){
        System.out.println("-----sendSMS");
    }
    // 非synchronized 修饰, 不受类锁和对象锁影响
    public void sendHello(){
        System.out.println("-----sendHello");
    }
}

/**
 * 执行结果:
 * -----sendHello
 * -----sendEmail
 * -----sendSMS
 * 
 * */

注意:类锁和对象锁不是一把锁,并不构成竞争条件 

 class  Phone {
    public static synchronized void sendEmail(){
        try { TimeUnit.SECONDS.sleep(3);} catch (InterruptedException e) {e.printStackTrace();}
        System.out.println("-----sendEmail");
    }
    // 对象锁, 不受类锁影响
    public synchronized void sendSMS(){
        System.out.println("-----sendSMS");
    }
    // 非synchronized 修饰, 不受类锁和对象锁影响
    public void sendHello(){
        System.out.println("-----sendHello");
    }
}
/**
 * 执行结果:
 * -----sendSMS
 * -----sendHello
 * -----sendEmail
 *
 * */

3)synchronized  --- 静态代码块

// this 代表当前锁住的对象资源
synchronized (this){
          
    System.out.println("-----sendHello");
}

4)缺点:由于线程阻塞引起的线程频繁睡眠和唤醒极大的消耗了 CPU 资源

三. 乐观锁--- CAS + 版本号机制:

        当多个线程竞争一个共享资源时,只有一个线程会胜出,但是其它竞争失败的线程并不会放弃占用cpu,而是循环刷新访问对于该资源锁的请求,直到获取该资源的锁;CAS只能保证变量的原子性并不能保证变量的可见性,所以一般都是使用 CAS + Volatile 实现变量的多线程

1. JUC 并发包中的原子类都存放在 java.util.concurrent.atomic 类路径下

        原子类创建的变量,通过原子类提供的 CAS 方法可以实现该变量在多线程情况下的线程安全性 

 1)当没有使用原子类时:  

public class Main {
    volatile int number = 0;    
    // 读操作
    public int getNumber(){
        return this.number;
    }
    // 写操作 必须加锁
    public synchronized void writeNumber(){
        this.number++;
    }
}

2)有了原子类之后:

public class Thread01 {
    public static void main(String[] args) {
        AtomicInteger number = new AtomicInteger(0);
        // 获取 number 的值
        number.get();
        // 设置 number 的值
        number.set(123);
    }
}

2. CAS 原理:
        CAS 内部维护需要读写的内存值(V)、expecValue(A)、newValue(B)

 

        CAS 是一条 CPU 原子指令(cmpxchg 指令),是依靠 Unsafe 类的 CAS 方法如(compareAndSwapxxxx)底层实现即为CPU指令 cmpxchg

   用 do-while 循环来实现 CAS 循环

    @HotSpotIntrinsicCandidate
    public final int getAndAddInt(Object o, long offset, int delta) {
        int v;
        do {
            v = getIntVolatile(o, offset);
        } while (!weakCompareAndSetInt(o, offset, v, v + delta));
        return v;
    }

3. 简单测试

        可以看到expectValue = 11,newValue = 20,因为内存旧值 = 10,不等于 expectValue,说明在这段时间内有其他线程操作了对象 atomicInteger ,修改了其值,所以本次修改失败,修改结果res = false,atomicInteger.get() = 10

import java.util.concurrent.atomic.AtomicInteger;
public class TestConCurrent {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(10);

        boolean res = atomicInteger.compareAndSet(11, 20);
        System.out.println("原子对象的修改结果:"+res);

        System.out.println(atomicInteger.get());
    }
}

4. 自旋CAS缺点:

  • 未获得锁的线程如果一直循环执行,会给CPU带来非常大的执行开销
  • CAS 保证的是对一个变量执行操作的原子性,如果对多个变量操作时,CAS 目前无法直接保证操作的原子性
  • ABA 问题:当某线程提交操作进行比较时发现内存数据旧数据A和预期值A一致,但是可能该数据被其他线程修改成B,然后又被其他线程修改回A;真正要做到严谨的CAS机制,我们在compare阶段不仅要比较期望值A和地址V中的实际值,还要比较变量的版本号是否一致
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值