前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >JUC并发编程(二)认识volatile,单例模式,各种锁

JUC并发编程(二)认识volatile,单例模式,各种锁

作者头像
HcodeBlogger
发布2020-07-14 10:53:55
3160
发布2020-07-14 10:53:55
举报
文章被收录于专栏:Hcode网站Hcode网站

MM(Java内存模型)

JMM : Java内存模型,不存在的东西,概念!约定! 关于JMM的一些同步的约定: 1. 线程解锁前,必须把共享变量立刻刷回主存。 2. 线程加锁前,必须读取主存中的新值到工作内存中! 3. 加锁和解锁是同一把锁

内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类 型的变量来说,load、store、read和write操作在某些平台上允许例外)

内存交互操作   内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)

  • lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
  • unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
  • read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
  • load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
  • use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
  • assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
  • store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
  • write  (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中

JMM对这八种指令的使用,制定了如下规则:

  • 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
  • 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
  • 不允许一个线程将没有assign的数据从工作内存同步回主内存
  • 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操作之前,必须经过assign和load操作
  • 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
  • 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
  • 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
  • 对一个变量进行unlock操作之前,必须把此变量同步回主内存

Volatile

特点:

  1. 保证可见性,即防止多个线程时,有线程操作了主存中的值,但其它线程不知道值被修改过了。
  2. 不保证原子性(可以用原子类解决)
  3. 禁止指令重排

原子类

依旧是JUC包下的类

在这里插入图片描述
在这里插入图片描述

测试:

代码语言:javascript
复制
COPYpublic class VDemo02 {

    // volatile 不保证原子性
    // 原子类的 Integer
    private volatile static AtomicInteger num = new AtomicInteger();

    public static void add(){
        // num++; // 不是一个原子性操作
        num.getAndIncrement(); // AtomicInteger + 1 方法, CAS
    }

    public static void main(String[] args) {

        //理论上num结果应该为 2 万
        for (int i = ; i <= ; i++) {
            new Thread(()->{
                for (int j = ; j <  ; j++) {
                    add();
                }
            }).start();
        }

        while (Thread.activeCount()>){ // main  gc
            Thread.yield();
        }

        System.out.println(Thread.currentThread().getName() + " " + num);


    }
}

禁止指令重排

使用内存屏障。CPU指令。作用: 1. 保证特定的操作的执行顺序! 2. 可以保证某些变量的内存可见性 (利用这些特性volatile实现了可见性)

在这里插入图片描述
在这里插入图片描述

Volatile 是可以保持 可见性。不能保证原子性,由于内存屏障,可以保证避免指令重排的现象产生

单例模式

饿汉式(可能浪费资源)

代码语言:javascript
复制
COPYpublic class Hungry {

    // 可能会浪费空间
    private byte[] data1 = new byte[*];
    private byte[] data2 = new byte[*];
    private byte[] data3 = new byte[*];
    private byte[] data4 = new byte[*];

    private Hungry(){

    }

    private final static Hungry HUNGRY = new Hungry();

    public static Hungry getInstance(){
        return HUNGRY;
    }

}

DCL 懒汉式

代码语言:javascript
复制
COPYpublic class LazyMan {

    private static boolean hcode = false;

    private LazyMan(){
        synchronized (LazyMan.class){
            if (hcode == false){
                hcode= true;
            }else {
                throw new RuntimeException("不要试图使用反射破坏异常");
            }
        }
    }

    private volatile static LazyMan lazyMan;

    // 双重检测锁模式的 懒汉式单例  DCL懒汉式
    public static LazyMan getInstance(){
        if (lazyMan==null){
            synchronized (LazyMan.class){
                if (lazyMan==null){
                    lazyMan = new LazyMan(); // 不是一个原子性操作
                }
            }
        }
        return lazyMan;
    }

    // 反射破坏~
    public static void main(String[] args) throws Exception {
//        LazyMan instance = LazyMan.getInstance();

        Field hcode= LazyMan.class.getDeclaredField("hcode");
        qinjiang.setAccessible(true);

        Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        LazyMan instance = declaredConstructor.newInstance();

        hcode.set(instance,false);

        LazyMan instance2 = declaredConstructor.newInstance();

        System.out.println(instance);
        System.out.println(instance2);
    }

}

枚举实现懒汉式(安全)

代码语言:javascript
复制
COPY// enum 是一个什么? 本身也是一个Class类
public enum EnumSingle {

    INSTANCE;
    public EnumSingle getInstance(){
        return INSTANCE;
    }

}

CAS

CAS : 比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就 一直循环! 缺点: 1. 循环会耗时 2. 一次性只能保证一个共享变量的原子性 3. ABA问题 (线程操作后又将标志位改回去了)

ABA问题 解决方法

原子引用:使用乐观锁的思想,给CAS加上版本号

代码语言:javascript
复制
COPYpublic class CASDemo {

    //AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题

    // 正常在业务操作,这里面比较的都是一个个对象
    static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(,);

    // CAS  compareAndSet : 比较并交换!
    public static void main(String[] args) {

        new Thread(()->{
            int stamp = atomicStampedReference.getStamp(); // 获得版本号
            System.out.println("a1=>"+stamp);

            try {
                TimeUnit.SECONDS.sleep();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            Lock lock = new ReentrantLock(true);

            atomicStampedReference.compareAndSet(, ,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + );

            System.out.println("a2=>"+atomicStampedReference.getStamp());


            System.out.println(atomicStampedReference.compareAndSet(, ,
                    atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + ));

            System.out.println("a3=>"+atomicStampedReference.getStamp());

        },"a").start();


        // 乐观锁的原理相同!
        new Thread(()->{
            int stamp = atomicStampedReference.getStamp(); // 获得版本号
            System.out.println("b1=>"+stamp);

            try {
                TimeUnit.SECONDS.sleep();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(atomicStampedReference.compareAndSet(, ,
                    stamp, stamp + ));

            System.out.println("b2=>"+atomicStampedReference.getStamp());

        },"b").start();

    }
}

Integer 使用了对象缓存机制,默认范围是 -128 ~ 127 ,推荐使用静态工厂方法 valueOf 获取对象实 例,而不是 new,因为 valueOf 使用缓存,而 new 一定会创建新的对象分配新的内存空间;

各种锁

公平锁、非公平锁

  • 公平锁: 非常公平, 不能够插队,必须先来后到!
  • 非公平锁:非常不公平,可以插队 (默认都是非公平锁)消耗时间少的先执行。 ReentrantLock()的添加参数,true为公平锁,默认false为非公平锁

可重入锁

只要拿到类方法的锁,方法里面引用其它方法的锁也就能自动获取到了。

自旋锁

就是while死循环等待版本号标志成立才执行。CAS的底层就是这样的。

在这里插入图片描述
在这里插入图片描述

例子:

代码语言:javascript
复制
COPYpublic class SpinlockDemo {

    // int   0
    // Thread  null
    AtomicReference<Thread> atomicReference = new AtomicReference<>();

    // 加锁
    public void myLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "==> mylock");

        // 自旋锁
        while (!atomicReference.compareAndSet(null,thread)){

        }
    }


    // 解锁
    // 加锁
    public void myUnLock(){
        Thread thread = Thread.currentThread();
        System.out.println(Thread.currentThread().getName() + "==> myUnlock");
        atomicReference.compareAndSet(thread,null);
    }



}

死锁

死锁就是双方都想获取对方持有的锁,导致程序停滞。

在这里插入图片描述
在这里插入图片描述

如何解决死锁

  1. 在当前idea程序执行时,在终端使用 jps -l 定位进程号。
  2. 使用 jstack 进程号 找到死锁问题
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2020年6月29日 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • MM(Java内存模型)
  • 单例模式
  • CAS
  • 各种锁
    • 公平锁、非公平锁
      • 可重入锁
        • 自旋锁
          • 死锁
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档