JUC学习
文章目录
15、异步回调
Future 设计的初衷:对将来的某个事件结果进行建模!
其实就是前端 —》发送ajax异步请求给后端
但是我们平时都使用CompletableFuture
(1)没有返回值的runAsync异步回调
package com.zmz.Async; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; /** * @ProjectName: Juc * @Package: com.zmz.Async * @ClassName: runAsync * @Author: 张晟睿 * @Date: 2021/10/11 18:58 * @Version: 1.0 */ public class runAsync { public static void main(String[] args) throws ExecutionException, InterruptedException { // 发起 一个 请求 System.out.println(System.currentTimeMillis()); System.out.println("---------------------"); CompletableFuture<Void> future = CompletableFuture.runAsync(()->{ //发起一个异步任务 try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"....."); }); System.out.println(System.currentTimeMillis()); System.out.println("------------------------------"); //输出执行结果 System.out.println(future.get()); //获取执行结果 } }
(2)有返回值的异步回调supplyAsync
package com.zmz.Async; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; /** * @ProjectName: Juc * @Package: com.zmz.Async * @ClassName: supplyAsync * @Author: 张晟睿 * @Date: 2021/10/11 19:09 * @Version: 1.0 */ public class supplyAsync { public static void main(String[] args) throws ExecutionException, InterruptedException { //有返回值的异步回调 CompletableFuture<Integer> completableFuture=CompletableFuture.supplyAsync(()->{ System.out.println(Thread.currentThread().getName()); try { TimeUnit.SECONDS.sleep(2); int i=1/0; } catch (InterruptedException e) { e.printStackTrace(); } return 1024; }); System.out.println(completableFuture.whenComplete((t, u) -> { /*我们可以看到whenComplete以上的程序有两个参数,一个是t 一个是u T:是代表的 正常返回的结果; U:是代表的 抛出异常的错误信息; 如果发生了异常,get可以获取到exceptionally返回的值; */ //success 回调 System.out.println("t=>" + t); //正常的返回结果 System.out.println("u=>" + u); //抛出异常的 错误信息 }).exceptionally((e) -> { //error回调 System.out.println(e.getMessage()); return 404; }).get()); } }
16、JMM(Java Memory Model )
1)我们先了解一下什么JMM?
JMM:JAVA内存模型,不存在的东西,抽象的,是一个概念,也是一个约定!
关于JMM的一些同步的约定:
- 线程解锁前,必须把共享变量立刻刷回主存
- 线程加锁前,必须读取主存中的最新值到工作内存中
- 加锁和解锁是同一把锁
线程中分为 工作内存、主内存。
八种操作:
名称 | 描述 |
Read(读取) | 作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用 |
load(载入) | 作用于工作内存的变量,它把read操作从主存中变量放入工作内存中 |
Use(使用) | 作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令 |
assign(赋值) | 作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中 |
store(存储) | 作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用 |
write(写入) | 作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中 |
lock(锁定) | 作用于主内存的变量,把一个变量标识为线程独占状态 |
unlock(解锁) | 作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定 |
对于八种操作给了相应的规定:
- 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
- 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
- 不允许一个线程将没有assign的数据从工作内存同步回主内存
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作
- 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
- 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
- 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
- 对一个变量进行unlock操作之前,必须把此变量同步回主内存
遇到问题:程序不知道主存中的值已经被修改过了!
17、volatile
1)保证可见性
package com.zmz.JMM; import java.util.concurrent.TimeUnit; /** * @ProjectName: Juc * @Package: com.zmz.JMM * @ClassName: JMMdemo01 * @Author: 张晟睿 * @Date: 2021/10/11 20:43 * @Version: 1.0 */ public class JMMdemo01 { // 如果不加volatile 程序会死循环 // 加了volatile是可以保证可见性的 private volatile static Integer number = 0; public static void main(String[] args) { //main线程 //子线程1 new Thread(()->{ while (number==0){ } }).start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } //子线程2 new Thread(()->{ while (number==0){ } }).start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } number=1; System.out.println(number); } }
2)不保证原子性
原子性:意思就是说不可分割,举一个例子就是说线程A在执行任务的时候,不能被打扰的,也不能被分割的,要么同时成功,要么同时失败。
package com.zmz.JMM; /** * @ProjectName: Juc * @Package: com.zmz.JMM * @ClassName: JMMdemo02 * @Author: 张晟睿 * @Date: 2021/10/11 20:46 * @Version: 1.0 */ public class JMMdemo02 { private static volatile int num = 0; public static void add(){ num++; //++ 不是一个原子性操作,是2个~3个操作 } public static void main(String[] args) { //理论上number === 20000 for (int i = 1; i <= 20; i++) { new Thread(()->{ for (int j = 1; j <= 1000 ; j++) { add(); } }).start(); } while (Thread.activeCount()>2){ //main gc Thread.yield(); } System.out.println(Thread.currentThread().getName()+",num="+num); } }
使用原子类
package com.zmz.JMM; import java.util.concurrent.atomic.AtomicInteger; /** * @ProjectName: Juc * @Package: com.zmz.JMM * @ClassName: JMMdemo03 * @Author: 张晟睿 * @Date: 2021/10/11 21:05 * @Version: 1.0 */ public class JMMdemo03 { private static volatile AtomicInteger number = new AtomicInteger(); public static void add(){ // number++; number.incrementAndGet(); //底层是CAS保证的原子性 } public static void main(String[] args) { //理论上number === 20000 for (int i = 1; i <= 20; i++) { new Thread(()->{ for (int j = 1; j <= 1000 ; j++) { add(); } }).start(); } while (Thread.activeCount()>2){ //main gc Thread.yield(); } System.out.println(Thread.currentThread().getName()+",num="+number); } }
这些类的底层都直接和操作系统挂钩!是在内存中修改值。
3)禁止指令重排
什么是指令重排?
我们写的程序,计算机并不是按照我们自己写的那样去执行的
源代码–>编译器优化重排–>指令并行也可能会重排–>内存系统也会重排–>执行
处理器在进行指令重排的时候,会考虑数据之间的依赖性!
int x=2; //1 int y=4; //2 x=x+10; //3 y=x*x; //4 //我们期望的执行顺序是 1_2_3_4 可能执行的顺序会变成3124 1423 //可不可能是 4123? 不可能的 1234567
可能造成的影响结果:前提:a b x y这四个值 默认都是0
线程A | 线程B |
x=a | y=b |
b=1 | a=2 |
正常的结果: x = 0; y =0
线程A | 线程B |
b=1 | a=2 |
x=a | y=b |
可能在线程A中会出现,先执行b=1,然后再执行x=a
在B线程中可能会出现,先执行a=2,然后执行y=b
那么就有可能结果如下:x=4; y=2
volatile可以避免指令重排:
volatile中会加一道内存的屏障,这个内存屏障可以保证在这个屏障中的指令顺序。
内存屏障:CPU指令。作用:保证特定的操作的执行顺序;可以保证某些变量的内存可见性(利用这些特性,就可以保证volatile实现的可见性)
4)总结
- 由于内存屏障,可以保证避免指令重排的现象产生
- 不能保证原子性
- volatile可以保证可见性
🥰面试题:在哪里用这个内存屏障用得最多呢?
单例模式
18、单例模式
1)饿汉式
package single; //饿汉式单例模式 @SuppressWarnings("all") public class Hungry { private byte[] date1= new byte[1024*1024]; private byte[] date2= new byte[1024*1024]; private byte[] date3= new byte[1024*1024]; private byte[] date4= new byte[1024*1024]; private Hungry(){ } private final static Hungry HUNGRY = new Hungry(); public static Hungry getInstance(){ return HUNGRY; } }
2)DCL懒汉式
package single; import java.lang.reflect.Constructor; //懒汉式 public class LazyMan { private LazyMan(){ synchronized(LazyMan.class){ throw new RuntimeException("不要试图使用反射破坏异常"); } // System.out.println(Thread.currentThread().getName()+"ok"); } private static LazyMan lazyMan; //双重检测锁 public static LazyMan getInstance(){ if (lazyMan==null) { synchronized (LazyMan.class) { if (lazyMan==null) { lazyMan = new LazyMan(); //不是原子性操作 //1.分配内存空间 //2.执行构造方法,初始化对象 //3.把这个对象指向空间 } } } return lazyMan; } public static void main(String[] args) throws Exception { // LazyMan instance = LazyMan.getInstance(); Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); LazyMan instance = declaredConstructor.newInstance(); LazyMan instance2 = declaredConstructor.newInstance(); System.out.println(instance); System.out.println(instance2); for (int i = 0; i < 2; i++) { new Thread(()->{ LazyMan.getInstance(); }).start(); } } }
3)静态内部类
package single; public class Holder { private Holder(){} private static Holder getInstance(){ return InnerClass.HOLDER; } public static class InnerClass { private static final Holder HOLDER = new Holder(); } }
单例不安全, 主要的原因是因为反射。
4)枚举
package com.zmz.Singleton; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; /** * @ProjectName: Juc * @Package: com.zmz.Singleton * @ClassName: EnumSingle * @Author: 张晟睿 * @Date: 2021/10/12 20:06 * @Version: 1.0 */ //enum 是什么? enum本身就是一个Class 类 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); //java.lang.NoSuchMethodException: com.ogj.single.EnumSingle.<init>() EnumSingle instance2 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } }
枚举类型的最终反编译源码:
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/ogj/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
1)什么是CAS?
CAS(Compare And Swap比较并替换),包含三个值当前内存值(V)、预期原来的值(A)以及期待更新的值(B),这里我们先做简单的介绍后期会出一期博文进行单独介绍的。
package com.zmz.CAS; import java.util.concurrent.atomic.AtomicInteger; /** * @ProjectName: Juc * @Package: com.zmz.CAS * @ClassName: CasDemo01 * @Author: 张晟睿 * @Date: 2021/10/12 20:15 * @Version: 1.0 */ public class CasDemo01 { //CAS : compareAndSet 比较并交换 public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(2020); //boolean compareAndSet(int expect, int update) //期望值、更新值 //如果实际值 和 我的期望值相同,那么就更新 //如果实际值 和 我的期望值不同,那么就不更新 System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); //因为期望值是2020 实际值却变成了2021 所以会修改失败 //CAS 是CPU的并发原语 atomicInteger.getAndIncrement(); //++操作 System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); } }
我们点开AtomicInteger源码观察一下,可以发现
2)总结
CAS:比较当前工作内存中的值 和 主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环,使用的是自旋锁。
缺点:
- 一次性只能保证一个共享变量的原子性
- 循环会耗时
- 它会存在ABA问题
什么是ABA问题?(就是我们所说的狸猫换太子)
package com.zmz.CAS; import java.util.concurrent.atomic.AtomicInteger; /** * @ProjectName: Juc * @Package: com.zmz.CAS * @ClassName: CasDemo02 * @Author: 张晟睿 * @Date: 2021/10/12 20:40 * @Version: 1.0 */ public class CasDemo02 { //CAS : compareAndSet 比较并交换 public static void main(String[] args) { AtomicInteger atomicInteger = new AtomicInteger(2020); System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); //boolean compareAndSet(int expect, int update) //期望值、更新值 //如果实际值 和 我的期望值相同,那么就更新 //如果实际值 和 我的期望值不同,那么就不更新 System.out.println(atomicInteger.compareAndSet(2021, 2020)); System.out.println(atomicInteger.get()); //因为期望值是2020 实际值却变成了2021 所以会修改失败 //CAS 是CPU的并发原语 // atomicInteger.getAndIncrement(); //++操作 System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); } }
20、原子引用
为了解决ABA问题,对应的思想:就是使用了乐观锁~
注意:
Integer 使用了对象缓存机制,默认范围是-128~127,推荐使用静态工厂方法valueOf获取对象实例,而不是new,因为valueOf使用缓存,而new一定会创建新的对象分配新的内存空间。
说明:对于Integer var = ?在-128 至127之间的赋值,Integer对象是在IntegerCache .cache产生,会复用已有对象, 这个区间内的Integer值可以直接使用==进 行判断,但是这个区间之外的所有数据,都会在堆上产生,并不会复用已有对象,这是一个大坑,推荐使用equals方法进行判断。
package com.zmz.CAS; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicStampedReference; /** * @ProjectName: Juc * @Package: com.zmz.CAS * @ClassName: CasDemo03 * @Author: 张晟睿 * @Date: 2021/10/12 20:45 * @Version: 1.0 */ public class CasDemo03 { /**AtomicStampedReference 注意,如果泛型是一个包装类,注意对象的引用问题 * 正常在业务操作,这里面比较的都是一个个对象 */ static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1); // CAS compareAndSet : 比较并交换! public static void main(String[] args) { new Thread(() -> { int stamp = atomicStampedReference.getStamp(); // 获得版本号 System.out.println("a1=>" + stamp); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } // 修改操作时,版本号更新 + 1 atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); System.out.println("a2=>" + atomicStampedReference.getStamp()); // 重新把值改回去, 版本号更新 + 1 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, 3, stamp, stamp + 1)); System.out.println("b2=>" + atomicStampedReference.getStamp()); }, "b").start(); } }
21、各种锁的理解
1)公平锁,非公平锁
- 公平锁:非常公平的锁,不能插队,必须先来后到
- 非公平锁:非常不公平,允许插队,可以改变顺序
2)可重入锁
1.Synchonized锁
package com.zmz.lock; /** * @ProjectName: Juc * @Package: com.zmz.lock * @ClassName: SynchonizedDemo * @Author: 张晟睿 * @Date: 2021/10/12 20:59 * @Version: 1.0 */ public class SynchonizedDemo { 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()+"=> 发短息"); call();//这里也有一把锁 } public synchronized void call(){ System.out.println(Thread.currentThread().getName()+"=> 打电话"); } }
2.Lock锁
package com.zmz.lock; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * @ProjectName: Juc * @Package: com.zmz.lock * @ClassName: LockDemo * @Author: 张晟睿 * @Date: 2021/10/12 21:02 * @Version: 1.0 */ public class LockDemo { 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()+"=> 发短信"); call();//这里也有一把锁 } catch (Exception e) { e.printStackTrace(); }finally { lock.unlock(); } } public void call(){ lock.lock(); try { System.out.println(Thread.currentThread().getName() + "=> 打电话"); }catch (Exception e){ e.printStackTrace(); } finally { lock.unlock(); } } }
注意:
- lock锁必须配对,相当于lock和 unlock 必须数量相同
- 在外面加的锁,也可以在里面解锁;在里面加的锁,在外面也可以解锁
3)自旋锁
1.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; }
2.自我设计自旋锁
package com.zmz.lock; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.locks.ReentrantLock; /** * @ProjectName: Juc * @Package: com.zmz.lock * @ClassName: MySpinlockTest * @Author: 张晟睿 * @Date: 2021/10/12 21:07 * @Version: 1.0 */ class MySpinlock { // 默认 // int 0 //thread null AtomicReference<Thread> atomicReference=new AtomicReference<>(); //加锁 public void myLock(){ Thread thread = Thread.currentThread(); System.out.println(thread.getName()+"===> mylock"); //自旋锁 while (!atomicReference.compareAndSet(null,thread)){ System.out.println(Thread.currentThread().getName()+" ==> 自旋中~"); } } //解锁 public void myUnlock(){ Thread thread=Thread.currentThread(); System.out.println(thread.getName()+"===> myUnlock"); atomicReference.compareAndSet(thread,null); } } public class MySpinlockTest { public static void main(String[] args) throws InterruptedException { ReentrantLock reentrantLock = new ReentrantLock(); reentrantLock.lock(); reentrantLock.unlock(); //使用CAS实现自旋锁 MySpinlock spinlockDemo=new MySpinlock(); new Thread(()->{ spinlockDemo.myLock(); try { TimeUnit.SECONDS.sleep(3); } catch (Exception e) { e.printStackTrace(); } finally { spinlockDemo.myUnlock(); } },"Thread1").start(); TimeUnit.SECONDS.sleep(1); new Thread(()->{ spinlockDemo.myLock(); try { TimeUnit.SECONDS.sleep(3); } catch (Exception e) { e.printStackTrace(); } finally { spinlockDemo.myUnlock(); } },"Thread2").start(); } }
运行结果:t2进程必须等待t1进程Unlock后,才能Unlock,在这之前进行自旋等待。
4)死锁
package com.zmz.lock; import java.util.concurrent.TimeUnit; /** * @ProjectName: Juc * @Package: com.zmz.lock * @ClassName: DeadLockDemo * @Author: 张晟睿 * @Date: 2021/10/12 21:13 * @Version: 1.0 */ public class DeadLockDemo { public static void main(String[] args) { String lockA= "lockA"; String lockB= "lockB"; new Thread(new MyThread1(lockA,lockB),"Thread1").start(); new Thread(new MyThread1(lockB,lockA),"Thread2").start(); } } class MyThread1 implements Runnable{ private String lockA; private String lockB; public MyThread1(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定位进程号,jdk的bin目录下: 有一个jps
jps -l
2、进程进程号 找到死锁信息
jstack 进程号 #如果出现拒绝访问 一定要以管理员身份运行
一般情况信息在最后: