18彻底玩转 单例模式

饿汉式 DCL懒汉模式 探究!

饿汉式

 package com.kuang.single;
 //饿汉式单例
 //单例模式重要思想是构造器私有
 public class Hungry {
 ​
     //可能会浪费空间
     private byte[] data1 = new byte[1024*1024];
     private byte[] data2 = new byte[1024*1024];
     private byte[] data3 = new byte[1024*1024];
     private byte[] data4 = new byte[1024*1024];
 ​
     private  Hungry(){
 ​
    }
     private final static Hungry HUNGRY = new Hungry();
 ​
     public static Hungry getInstance(){
         return HUNGRY;
    }
 }
 ​

DCL懒汉式

 package com.kuang.single;
 ​
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 ​
 //懒汉式单例
 public class LayzMan {
     private static boolean qinjiang = false;
 ​
     private LayzMan() {
 ​
             synchronized (LayzMan.class){
                 if (qinjiang == false){
                     qinjiang = true;
                }
                 else {
                     throw new RuntimeException("不要试图使用反射破坏异常");
                }
 ​
            }
        }
 ​
 ​
 ​
 ​
     private volatile static LayzMan layzMan;
 ​
     public static LayzMan getInstance() {
         //加锁
         //双重检测锁模式的 懒汉式单例 DCL懒汉模式
         if (layzMan == null) {
             synchronized (LayzMan.class) {//锁class只有一个
                 if (layzMan == null) {
                     layzMan = new LayzMan();//不是一个原子性操作
 ​
                }
            }
        }
 ​
         return layzMan;//加了线程B后 此时layzMan还没有完成构造
    }
     /**
      *1.分配内存空间
      * 2.执行构造方法,初始化对象
      * 3.把这个对象指向这个空间
      *
      * 假设原本希望执行123
      * 真实可能执行132 若只有单线程A可以执行 若再加了一个线程B会出现问题 了线程B后 此时layzMan还没有完成构造
      */
     //反射
 ​
     public static void main(String[] args) throws Exception {
      // LayzMan instance = LayzMan.getInstance();
 ​
         Field qinjiang = LayzMan.class.getDeclaredField("qinjiang");
         qinjiang.setAccessible(true);
         Constructor<LayzMan> declaredConstructor = LayzMan.class.getDeclaredConstructor(null);
         declaredConstructor.setAccessible(true);
         LayzMan instance = declaredConstructor.newInstance();
         qinjiang.set(instance,false);
         LayzMan instance2 = declaredConstructor.newInstance();
 ​
         System.out.println(instance);
         System.out.println(instance2);
    }
 ​
 }

静态内部类

 package com.kuang.single;
 //静态内部类 在一个类里面再写一个静态类
 public class Holder {
     private Holder(){
 ​
    }
 ​
     public static Holder getInstance(){
         return InnerClass.HOLDER;
    }
 ​
     public static class InnerClass{
         private static final Holder HOLDER = new Holder();
 ​
    }
 }
 ​

单例不安全,存在反射

枚举enum

 package com.kuang.single;
 ​
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 ​
 //enum (枚举)是个什么? 枚举本身也是一个Class类
 //反射不能破坏枚举的单例Cannot reflectively create enum objects
 public enum EnumSingle {
 ​
 ​
     INSTANCE;
     public EnumSingle getInstance(){
         return INSTANCE;
    }
 }
 ​
 class Test{
     public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
         EnumSingle instance1 = EnumSingle.INSTANCE;
         Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
         declaredConstructor.setAccessible(true);
         EnumSingle instance2 = declaredConstructor.newInstance();
         System.out.println(instance1);
         System.out.println(instance2);
 ​
         //NoSuchMethodException: com.kuang.single.EnumSingle.<init>()这个类里面没有空参构造器
 ​
 ​
    }
 }

枚举为有参构造 两个参数

把一个class文件反编译为java的操作:

枚举类型的最终反编译源码:

 // Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
 // Jad home page: http://www.kpdus.com/jad.html
 // Decompiler options: packimports(3)
 // Source File Name:   EnumSingle.java
 ​
 package com.kuang.single;
 ​
 ​
 public final class EnumSingle extends Enum
 {
 ​
     public static EnumSingle[] values()
    {
         return (EnumSingle[])$VALUES.clone();
    }
 ​
     public static EnumSingle valueOf(String name)
    {
         return (EnumSingle)Enum.valueOf(com/kuang/single/EnumSingle, name);
    }
 ​
     private EnumSingle(String s, int i)
    {
         super(s, i);
    }
 ​
     public EnumSingle getInstance()
    {
         return INSTANCE;
    }
 ​
     public static final EnumSingle INSTANCE;
     private static final EnumSingle $VALUES[];
 ​
     static
    {
         INSTANCE = new EnumSingle("INSTANCE", 0);
         $VALUES = (new EnumSingle[] {
             INSTANCE
        });
    }
 }
 ​

19深入理解CAS

什么是CAS

Unsafe类

 package com.cas;
 ​
 import java.util.concurrent.atomic.AtomicInteger;
 ​
 public class CASDemo {
     //CAS compareAndSet比较并交换
 ​
 ​
     public static void main(String[] args) {
         AtomicInteger atomicInteger = new AtomicInteger(2021);
 ​
         //expect:期望 update:更新
 ​
         // public final boolean compareAndSet(int expect, int update)
 ​
         //如果我期望的值达到了,那么就更新,否则就不更新,CAS是CPU的并发原语
         System.out.println(atomicInteger.compareAndSet(2021, 2022));
         System.out.println(atomicInteger.get());
         atomicInteger.getAndIncrement();//
 ​
         System.out.println(atomicInteger.compareAndSet(2021, 2022));
         System.out.println(atomicInteger.get());
 ​
    }
 }
 ​

CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环!

缺点:

1.循环会耗时

2.一次性只能保证一个共享变量的原子性

3.存在ABA问题

CAS:ABA问题:(狸猫换太子)

 ​
 package com.cas;
 ​
 import java.util.concurrent.atomic.AtomicInteger;
 ​
 public class CASDemo {
     //CAS compareAndSet比较并交换
 ​
 ​
     public static void main(String[] args) {
         AtomicInteger atomicInteger = new AtomicInteger(2021);
         
 ​
         //expect:期望 update:更新
 ​
         // public final boolean compareAndSet(int expect, int update)
 ​
         //如果我期望的值达到了,那么就更新,否则就不更新,CAS是CPU的并发原语
         //====================捣乱的线程=========================
         System.out.println(atomicInteger.compareAndSet(2021, 2022));
         System.out.println(atomicInteger.get());
 ​
 ​
         System.out.println(atomicInteger.compareAndSet(2022, 2021));
         System.out.println(atomicInteger.get());
 ​
         //===========================期望的线程=====================
         System.out.println(atomicInteger.compareAndSet(2021, 6666));
         System.out.println(atomicInteger.get());
 ​
    }
 }
 ​

20.原子引用

带版本号的原子操作!

解决ABA问题,引入原子引用!对应的思想是乐观锁

 package com.cas;
 ​
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicReference;
 import java.util.concurrent.atomic.AtomicStampedReference;
 ​
 public class CASDemo {
     //CAS compareAndSet比较并交换
     // AtomicStampedReference注意,如果泛型是一个包装类,注意对象的引用问题
 ​
     //正常在业务操作,这里面比较的都是一个个对象
     static   AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
 ​
 ​
 ​
     public static void main(String[] args) {
         //AtomicInteger atomicInteger = new AtomicInteger(2021);
 ​
 ​
         new Thread(()->{
             int stamp = atomicStampedReference.getStamp();//获得版本号
             System.out.println("a1=>"+stamp);
             try {
                 TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                 e.printStackTrace();
            }
             //拿到最新的版本号,拿到之后再把这个值加一
           atomicStampedReference.compareAndSet(1, 2,
                     atomicStampedReference.getStamp(),
                     atomicStampedReference.getStamp() + 1);
 ​
             System.out.println("a2=>"+atomicStampedReference.getStamp());
             System.out.println(atomicStampedReference.compareAndSet(2, 1,
                     atomicStampedReference.getStamp(),
                     atomicStampedReference.getStamp() + 1));
             //获取最新的版本号
             System.out.println("a3=>"+atomicStampedReference.getStamp());
 ​
        },"a").start();
         new Thread(()->{
             int stamp = atomicStampedReference.getStamp();//获得版本号
             //乐观锁的原理相同
             System.out.println("b1=>"+stamp);
             try {
                 TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                 e.printStackTrace();
            }
             System.out.println(atomicStampedReference.compareAndSet(1, 6, stamp, stamp + 1));
             System.out.println("b2=>"+atomicStampedReference.getStamp());
 ​
 ​
        },"b").start();
 ​
 ​
 ​
 ​
    }
 }

注意

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

21.各种锁的理解

1.公平锁,非公平锁

公平锁:非常公平,不能够插队,线程必须先来后到!

非公平锁:非常不公平,可以插队(默认都是非公平的)

   public ReentrantLock() {
         sync = new NonfairSync();
    }
 ​
 转换为非公平锁
 public ReentrantLock(boolean fair) {
         sync = fair ? new FairSync() : new NonfairSync();
    }

2.可重入锁

可重入锁(递归锁)

Synchronized

 package com.kuang.lock;
 //Synchronized默认是非公平的
 public class Demo01 {
     public static void main(String[] args) {
         Phone phone = new Phone();
         new Thread(()->{
             phone.sms();
        },"A").start();
         new Thread(()->{
             phone.sms();
        },"B").start();
 ​
    }
 }
 class Phone{
     //synchronized只有一把锁
     public synchronized void sms(){
         System.out.println(Thread.currentThread().getName() + "sms");
         call();//这里也有锁
 ​
    }
     public synchronized void call(){
         System.out.println(Thread.currentThread().getName() + "call");
 ​
    }
 }

Lock锁

 package com.kuang.lock;
 ​
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
 ​
 public class Demo02 {
     public static void main(String[] args) {
         Phone2 phone = new Phone2();
         new Thread(()->{
             phone.sms();
        },"A").start();
         new Thread(()->{
             phone.sms();
        },"B").start();
 ​
    }
 }
 class Phone2{
     Lock lock = new ReentrantLock();
     public  void sms(){
         lock.lock();//细节问题:两把钥匙 第一把钥匙开外面的锁,另外一把开里面的锁
         //lock.lock()负责解lock.unlock();
         //lock 锁必须配对,否则就会死在里面
 ​
 ​
         try {
             System.out.println(Thread.currentThread().getName() + "sms");
             call();//这里也有锁
        } catch (Exception e) {
             e.printStackTrace();
        } finally {
             lock.unlock();
        }
 ​
 ​
    }
     public  void call(){
         lock.lock();
 ​
         try {
             System.out.println(Thread.currentThread().getName() + "call");
        } catch (Exception e) {
             e.printStackTrace();
        } finally {
             lock.unlock();
        }
 ​
    }
 }
 ​

3.自旋锁

spinlock

不断的尝试直到成功为止!

自定义锁来测试

 package com.kuang.lock;
 ​
 import java.util.concurrent.atomic.AtomicReference;
 ​
 /**
  * 自旋锁
  */
 public 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);
 ​
 ​
    }
 }
 ​

测试

 package com.kuang.lock;
 ​
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.locks.ReentrantLock;
 ​
 public class TestSpinLock {
     public static void main(String[] args) throws InterruptedException {
         //ReentrantLock reentrantLock = new ReentrantLock();
       // reentrantLock.lock();
        // reentrantLock.unlock();
 ​
         //底层使用的自旋锁CAS
         SpinlockDemo lock = new SpinlockDemo();
 ​
         new Thread(()->{
             lock.myLock();
             try {
                 TimeUnit.SECONDS.sleep(3);
 ​
            } catch (Exception e) {
                 e.printStackTrace();
            } finally {
                 lock.myUnLock();
            }
 ​
        },"T1").start();
 ​
         //延迟保证T1先获得锁
         //T1解锁之后才会释放 T2才有机会进去拿到锁并且把它解锁掉
         TimeUnit.SECONDS.sleep(1);
         new Thread(()->{
             lock.myLock();
             try {
                 TimeUnit.SECONDS.sleep(3);
 ​
            } catch (Exception e) {
                 e.printStackTrace();
            } finally {
                 lock.myUnLock();
            }
 ​
        },"T2").start();
 ​
 ​
 ​
    }
 }
 ​

结果:

T1==> mylock T2==> mylock T1==> myUnlock T2==> myUnlock

Process finished with exit code 0

4.死锁

死锁测试,怎么排除死锁

死锁:

 package com.kuang.lock;
 ​
 import lombok.SneakyThrows;
 ​
 import java.util.concurrent.TimeUnit;
 ​
 public class DeadLockDemo {
     public static void main(String[] args) {
         String lockA = "lockA";
         String lockB = "lockB";
 ​
 ​
         new Thread(new MyThread(lockA,lockB),"T1").start();
         new Thread(new MyThread(lockB,lockA),"T2").start();
 ​
    }
 }
 class MyThread implements Runnable{
 ​
 ​
     private String lockA;
     private String lockB;
 ​
    public MyThread(String lockA, String lockB) {
         this.lockA = lockA;
         this.lockB = lockB;
    }
 ​
     @SneakyThrows
     @Override
     public void run(){
        synchronized (lockA){
            //A想拿B
            System.out.println(Thread.currentThread().getName()+"lock:"+lockA+"=>get"+lockB);
 ​
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
 ​
            synchronized (lockB){
                //B想拿A
                System.out.println(Thread.currentThread().getName()+"lock:"+lockA+"=>get"+lockB);
 ​
            }
        }
 ​
    }
 ​
 }

解决问题

1.使用jps -l定位进程号

2.使用jstack进程号查看进程信息(查看怎么死锁的)

面试或者工作中:排查问题:

1.日志

2.查看堆栈信息

第47天打卡学习(单例模式 深入了解CAS 原子引用 各种锁的理解)的更多相关文章

  1. Python学习路线【对标大厂Python工程师的招聘要求,并推荐优质免费资源】打卡学习不迷茫

    您好,我是码农飞哥,感谢您阅读本文,欢迎一键三连哦. 本文要点:从Python爬虫工程师的招聘要求出发制定学习路线,同时还推荐免费优质的学习资源. 打卡学习不迷茫. 干货满满,建议收藏,需要用到时常看 ...

  2. 【第一期百题计划进行中,快来打卡学习】吃透java、细化到知识点的练习题及笔试题,助你轻松搞定java

    [快来免费打卡学习]参与方式 本期百题计划开始时间:2022-02-09,今日打卡题已在文中标红. 0.本文文末评论区打卡,需要登录才可以打卡以及查看其他人的打卡记录 1.以下练习题,请用对应的知识点 ...

  3. selenium 学习之路开始了,一遍搬一遍理解学习,加油!!!

    selenium 学习之路开始了,一遍搬一遍理解学习,加油!!!

  4. Java并发包源码学习系列:基于CAS非阻塞并发队列ConcurrentLinkedQueue源码解析

    目录 非阻塞并发队列ConcurrentLinkedQueue概述 结构组成 基本不变式 head的不变式与可变式 tail的不变式与可变式 offer操作 源码解析 图解offer操作 JDK1.6 ...

  5. android学习笔记47——读写SD卡上的文件

    读写SD卡上的文件 通过Context的openFileInput.openFileOutput来打开文件输入流.输出流时,程序打开的都是应用程序的数据文件夹里的文件,其存储的文件大小可能都比较有限- ...

  6. ID卡学习笔记

    前言: 我也来篇关于当时学习ID卡的笔记.前段时间小区装门禁.一个钮扣型的ID卡就要30块.非常黑心.因为其ID卡的成本也就是1块钱以下.因此我也加入到这方面的研究.用来模拟ID卡的T5557卡成本2 ...

  7. (@WhiteTaken)设计模式学习——单例模式

    单例模式,个人理解就是,使用了这个模式,可以保证一个类只生成唯一的实例对象.就是在整个程序中,这个类只存在一个实例对象. GoF对单例模式的定义:保证一个类,只有一个实例存在,同时提供能对该实例加以访 ...

  8. java设计模式学习 ----- 单例模式(Singleton)

    单例模式(Singleton) 单例对象(Singleton)是一种经常使用的设计模式. 在Java应用中,单例对象能保证在一个JVM中,该对象仅仅有一个实例存在.单例模式也分三种:懒汉式单例.饿汉式 ...

  9. 从学习“单例模式”学到的Java知识:双重检查锁和延迟初始化

    一切真是有缘,上午刚刚看完单例模式,还在为其中的代码块同步而兴奋,下午就遇见这篇文章:双重检查锁定与延迟初始化.我一看,文章开头语出惊人,说这是一种错误的优化,我说,难道上午学的东西下午就过时了吗?仔 ...

随机推荐

  1. [OpenCV]获取摄像头视频

    环境:Windows 8 64bit + VS2012 X64 + OpenCV 2.4.7 摄像头的捕捉使用VideoCapture类 Class VideoCapture    [OpenCV文档 ...

  2. 【hdu 3579】Hello Kiki(数论--拓展欧几里德 求解同余方程组)

    题意:Kiki 有 X 个硬币,已知 N 组这样的信息:X%x=Ai , X/x=Mi (x未知).问满足这些条件的最小的硬币数,也就是最小的正整数 X. 解法:转化一下题意就是 拓展欧几里德求解同余 ...

  3. fiddler抓包+安卓机 完成手机app抓包的配置 遇到的一些问题

    fiddler抓包+安卓模拟器完成手机app抓包的配置:fiddler抓包+雷电模拟器 完成手机app抓包的配置 其实在安卓真机上弄比在虚拟机上弄更麻烦一点,它们的步骤都差不多一样,就是在安卓真机上弄 ...

  4. hdu3577 Fast Arrangement

    Problem Description Chinese always have the railway tickets problem because of its' huge amount of p ...

  5. Codeforces Round #681 (Div. 2, based on VK Cup 2019-2020 - Final) D. Extreme Subtraction (贪心)

    题意:有一个长度为\(n\)的序列,可以任意取\(k(1\le k\le n)\),对序列前\(k\)项或者后\(k\)减\(1\),可以进行任意次操作,问是否可以使所有元素都变成\(0\). 题解: ...

  6. Codeforces Round #642 (Div. 3) D. Constructing the Array (优先队列)

    题意:有一个长度为\(n\)元素均为\(0\)的序列,进行\(n\)次操作构造出一个新序列\(a\):每次选择最长的连续为\(0\)的区间\([l,r]\),使得第\(i\)次操作时,\(a[\fra ...

  7. net core 踩坑记录

    静态文件要放到wwwroot目录中才能访问 linux服务器部署运行报错 System.Net.Http.HttpRequestException: The SSL connection could ...

  8. CQRS+Event Sourcing

    using System; using System.Collections.Generic; using System.Linq; namespace CQRS { public class Eve ...

  9. zzuli-2266 number

    题目描述 某人刚学习了数位DP,他在某天忽然思考如下问题: 给定n,问有多少数对<x, y>满足: x, y∈[1, n], x < y x, y中出现的[0, 9]的数码种类相同 ...

  10. HashMap三百问

    文章目录: 一.JDK1.7之HashMap 二.JDK1.8之HashMap 三.Hashtable JDK1.7之HashMap 1. 定义 HashMap实现了Map接口,继承AbstractM ...