现在我们不用getInstance()去获取对象,而是直接通过反射创建两个对象
//反射! public static void main(String[] args) throws Exception { // LazyMan instance = LazyMan.getInstance(); Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); LazyMan instance2 = declaredConstructor.newInstance(); LazyMan instance = declaredConstructor.newInstance(); System.out.println(instance.hashCode()); System.out.println(instance2.hashCode()); }
可以发现,单例又被破坏了,
因为构造函数里面判断的是
但是注意,我们用反射new 的对象跟类里面的lazyman对象肯定是不一样的啊,没有调用getInstance(),类里面的lazyman就一直为空,所以单例又被破坏了
解决方法,用个标志位
private static boolean flag=false; private LazyMan(){ synchronized (LazyMan.class){ if (flag==false){ flag=true; }else { throw new RuntimeException("不用试图使用反射破坏异常"); } // if (lazyMan!=null){ // throw new RuntimeException("不用试图使用反射破坏异常"); // } } System.out.println(Thread.currentThread().getName()+"ok"); }
但是,再牛逼的加密也会解密
来我们继续破坏单例,我们把这个flag字段给它破坏了
//反射! public static void main(String[] args) throws Exception { // LazyMan instance = LazyMan.getInstance(); Field flag = LazyMan.class.getDeclaredField("flag"); flag.setAccessible(true); Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); LazyMan instance = declaredConstructor.newInstance(); flag.set(instance,false); LazyMan instance2 = declaredConstructor.newInstance(); System.out.println(instance.hashCode()); System.out.println(instance2.hashCode()); }
结果:可以发现单例又被破坏了
完整代码:
package com.kuang.single; import java.lang.reflect.Constructor; import java.lang.reflect.Field; //懒汉式单例 public class LazyMan { private static boolean flag=false; private LazyMan(){ synchronized (LazyMan.class){ if (flag==false){ flag=true; }else { throw new RuntimeException("不用试图使用反射破坏异常"); } // if (lazyMan!=null){ // throw new RuntimeException("不用试图使用反射破坏异常"); // } } System.out.println(Thread.currentThread().getName()+"ok"); } private volatile static LazyMan lazyMan; //双重锁机制 DCL懒汉式 public static LazyMan getInstance(){ if (lazyMan==null){ synchronized (LazyMan.class){ if (lazyMan==null){ lazyMan=new LazyMan(); /** * 1、分配内存空间 * 2、执行构造方法,初始化对象 * 3、把这个对象指向这个空间 * * 123 * 132 A * B */ } } } return lazyMan; } // //多线程并发 // public static void main(String[] args) { // for (int i = 0; i < 10; i++) { // new Thread(()->{ // LazyMan.getInstance(); // }).start(); // } // } //反射! public static void main(String[] args) throws Exception { // LazyMan instance = LazyMan.getInstance(); Field flag = LazyMan.class.getDeclaredField("flag"); flag.setAccessible(true); Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); LazyMan instance = declaredConstructor.newInstance(); flag.set(instance,false); LazyMan instance2 = declaredConstructor.newInstance(); System.out.println(instance.hashCode()); System.out.println(instance2.hashCode()); } }
那怎么解决呢?我们点进去反射的newInstance()看看呢
我们可以看到,如果类是一个枚举类型的话,就会告诉你不能使用反射破坏枚举,枚举是jdk 1.5 开始出现的,自带单例模式
4)枚举
枚举本身也是一个类
package com.kuang.single; //enum 是一个什么? 本身也是一个Class类 public enum EnumSingle { INSTANCE; public static EnumSingle getInstance() { return INSTANCE; } } class Test{ public static void main(String[] args) { EnumSingle instance1=EnumSingle.INSTANCE; EnumSingle instance2=EnumSingle.INSTANCE; EnumSingle instance3=EnumSingle.getInstance(); System.out.println(instance1); System.out.println(instance2); System.out.println(instance3); } }
我们来试试用反射破坏枚举单例
package com.kuang.single; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; //enum 是一个什么? 本身也是一个Class类 public enum EnumSingle { INSTANCE; public static EnumSingle getInstance() { return INSTANCE; } } class Test{ public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { EnumSingle instance1=EnumSingle.INSTANCE; Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); EnumSingle instance2 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } }
下面的错误提示是枚举类没有空参的构造方法
也就是下面这句话出错了idea骗了我们
//NoSuchMethodException: com.kuang.single.EnumSingle.<init>() Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
正常破坏单例是应该报错不能使用反射破坏枚举
通过反编译我们可以看到,这个枚举本身也是一个class,它继承了一个枚举类
然而构造器还是空参的啊,说明我们还是被骗了
现在我们用jad.exe反编译试试
jad.exe复制到.class文件夹下
我们把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 static EnumSingle getInstance() { return INSTANCE; } public static final EnumSingle INSTANCE; private static final EnumSingle $VALUES[]; static { INSTANCE = new EnumSingle("INSTANCE", 0); $VALUES = (new EnumSingle[] { INSTANCE }); } }
可以看到,不是无参构造器哦,而是有参构造器,有一个String,一个Int
现在我们修改反射代码
// Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null); Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
完整代码:
package com.kuang.single; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; //enum 是一个什么? 本身也是一个Class类 public enum EnumSingle { INSTANCE; public static EnumSingle getInstance() { return INSTANCE; } } class Test{ public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { EnumSingle instance1=EnumSingle.INSTANCE; //NoSuchMethodException: com.kuang.single.EnumSingle.<init>() // Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null); 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); } }
19、深入理解CAS
1)什么是CAS
大厂必须深入研究底层!!!!修内功!操作系统、计算机网络原理、组成原理、数据结构
package com.kuang.cas; import java.util.concurrent.atomic.AtomicInteger; public class CASDemo { //CAS public static void main(String[] args) { AtomicInteger atomicInteger=new AtomicInteger(2020); // 期望值、更新值 // public final boolean compareAndSet(int expect, int update) // 如果实际值 和 期望值相同,那么就更新 // 如果实际值 和 期望值不同,那么就不更新 System.out.println(atomicInteger.compareAndSet(2020, 2021));//true System.out.println(atomicInteger.get());//2021 //因为期望值是2020 实际值却变成了2021 所以会修改失败 //CAS 是CPU的并发原语 atomicInteger.getAndIncrement(); //++操作 System.out.println(atomicInteger.compareAndSet(2020, 2021));//false System.out.println(atomicInteger.get());//2021 } }
Java无法操作内存,但是C++可以操作内存,Java可以通过native方法调用c++从而操作内存
Unsafe 类
总结:
CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作,如果不是,就一直循环
好处:是不用切换线程状态,因为切换线程状态性能消耗比较大
缺点:
1:由于底层是自旋锁,循环会浪费时间
2:因为是底层的cpu操作,一次只能保证一个共享变量的原子性
3:ABA问题
CAS: ABA问题(狸猫换太子)
线程1:期望值是1,要变成2;
线程2:两个操作:
1、期望值是1,变成3
2、期望是3,变成1
所以对于线程1来说,A的值还是1,所以就出现了问题,骗过了线程1;
测试ABA问题:
package com.kuang.cas; import java.util.concurrent.atomic.AtomicInteger; public class CASDemo { //CAS public static void main(String[] args) { AtomicInteger atomicInteger=new AtomicInteger(2020); // 期望值、更新值 // public final boolean compareAndSet(int expect, int update) // 如果实际值 和 期望值相同,那么就更新 // 如果实际值 和 期望值不同,那么就不更新 //============捣乱的线程=========== System.out.println(atomicInteger.compareAndSet(2020, 2021));//true System.out.println(atomicInteger.get());//2021 System.out.println(atomicInteger.compareAndSet(2021, 2020));//true System.out.println(atomicInteger.get());//2021 // //因为期望值是2020 实际值却变成了2021 所以会修改失败 // //CAS 是CPU的并发原语 // atomicInteger.getAndIncrement(); //++操作 //============期望的线程=========== System.out.println(atomicInteger.compareAndSet(2020, 6666));//false System.out.println(atomicInteger.get());//2021 } }
结果:
如何解决ABA问题
原子引用
20、原子引用
解决ABA问题,引入原子引用;对应思想:乐观锁
带版本号的原子引用!
因为integer对象的问题,导致下面结果没达到预期。
compareAndSet()//返回false //Integer(2020)不会==Integer(2020) 当前值不会==期望值 expectedReference == current.reference //false //源码: public boolean compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) { Pair<V> current = pair; return expectedReference == current.reference && expectedStamp == current.stamp && ((newReference == current.reference && newStamp == current.stamp) || casPair(current, Pair.of(newReference, newStamp))); }
public static void main(String[] args) { AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(2020,1); new Thread(()->{ int stamp = atomicStampedReference.getStamp();//获得版本号 System.out.println("a1=>"+stamp); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicStampedReference.compareAndSet(2020, 2022, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1)); System.out.println("a2=>"+atomicStampedReference.getStamp()); System.out.println(atomicStampedReference.compareAndSet(2022, 2020, 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(2020, 6666, stamp, stamp + 1)); System.out.println("b2=>"+atomicStampedReference.getStamp()); },"b").start(); }
把2020->1,2022->2,6666->6
package com.kuang.cas; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicStampedReference; public class CASDemo { public static void main(String[] args) { AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1); //乐观锁的原理相同 new Thread(()->{ int stamp = atomicStampedReference.getStamp();//获得版本号 System.out.println("a1=>"+stamp); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(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(); } }
测试结果:
21、各种锁的理解
1、公平锁、非公平锁
公平锁:非常公平,不能插队,必须先来后到
/** * Creates an instance of {@code ReentrantLock}. * This is equivalent to using {@code ReentrantLock(false)}. */ public ReentrantLock() { sync = new NonfairSync(); }
非公平锁:非常不公平,允许插队,可以改变顺序,synchronized和lock(默认都是非公平锁)
/** * Creates an instance of {@code ReentrantLock} with the * given fairness policy. * * @param fair {@code true} if this lock should use a fair ordering policy */ public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
2、可重入锁
所有的锁都是可重入锁,有些地方叫做递归锁
你进入你家,拿到了大门的锁,也就自动拿到了里面小门的锁
1、Synchonized 锁
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{ public synchronized void sms(){ System.out.println(Thread.currentThread().getName()+"=> sms"); call();//这里也有一把锁 } public synchronized void call(){ System.out.println(Thread.currentThread().getName()+"=> call"); } }
2、Lock 锁
package com.kuang.lock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; //lock 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锁必须配对,否则就会死锁在里面 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(); } } }
注意细节:
- lock锁必须配对,相当于lock和 unlock 必须数量相同;
- 在外面加的锁,也可以在里面解锁;在里面加的锁,在外面也可以解锁;
3、自旋锁
spinlock
public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }
自己设计自旋锁
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)){ System.out.println(Thread.currentThread().getName()+" ==> 自旋中~"); } } //解锁 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; public class TestSpinLock { public static void main(String[] args) throws InterruptedException { //ReentrantLock reentrantLock = new ReentrantLock(); //reentrantLock.lock(); //reentrantLock.unlock(); //使用CAS实现自旋锁 SpinlockDemo spinlockDemo=new SpinlockDemo(); new Thread(()->{ spinlockDemo.myLock(); try { TimeUnit.SECONDS.sleep(3); } catch (Exception e) { e.printStackTrace(); } finally { spinlockDemo.myUnlock(); } },"t1").start(); TimeUnit.SECONDS.sleep(1); new Thread(()->{ spinlockDemo.myLock(); try { TimeUnit.SECONDS.sleep(3); } catch (Exception e) { e.printStackTrace(); } finally { spinlockDemo.myUnlock(); } },"t2").start(); } }
测试结果:
t1===> myLock t2===> myLock t2 ==> 自旋中~ t2 ==> 自旋中~ t2 ==> 自旋中~ t2 ==> 自旋中~ ... ... t2 ==> 自旋中~ t2 ==> 自旋中~ t2 ==> 自旋中~ t2 ==> 自旋中~ t1===> myUnlock t2 ==> 自旋中~ ===========t2进程必须等待t1进程Unlock后,才能Unlock,在这之前进行自旋等待====== t2===> myUnlock
4、死锁
联系操作系统
第三章 处理机调度和死锁【操作系统】
package com.kuang.lock; import java.util.concurrent.TimeUnit; public class DeadLock { public static void main(String[] args) { String lockX= "lockX"; String lockY= "lockY"; new Thread(new MyThread(lockX,lockY),"t1").start(); new Thread(new MyThread(lockY,lockX),"t2").start(); } } class MyThread implements Runnable{ private String lockA; private String lockB; public MyThread(String lockA, String lockB) { this.lockA = lockA; this.lockB = lockB; } @Override public void run() { synchronized (lockA){ System.out.println(Thread.currentThread().getName()+" lock"+lockA+"===>get"+lockB); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (lockB){ System.out.println(Thread.currentThread().getName()+" lock"+lockB+"===>get"+lockA); } } } }
结果:死锁
解决问题
1、使用jps -l
定位进程号,
查看哪一个进程出了问题
2、使用jstack [进程号]
查看进程的信息,找到死锁问题
面试,工作中!排查问题:
1、日志信息
2、堆栈信息
最后
关于本文
Markdown 74129 字数 4200 行数
HTML 66309 字数 2973 段落
本文依照狂神视频学习,
并参考,CSDN优秀博主的博文
掺杂自己的拙见
仅作学习交流使用
安利狂神说java
视频下狂神的评论:
[置顶]白漂有罪,拒绝白嫖,从点赞转发关注做起!
文章同步在公众号:狂神说 (公众号日更,记得关注)
视频文档地址:https://gitee.com/kuangstudy/openclass 记得三连转发