并发编程JUC(下)
2021/9/27 17:14:37
本文主要是介绍并发编程JUC(下),对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
16、JMM
Volatile的理解
Volatile 是 Java 虚拟机提供 轻量级的同步机制(与synchronized差不多,但是没有它强大)
1、保证可见性
2、不保证原子性
3、禁止指令重排
什么是JMM
JMM:Java内存模型,不存在的东西,是一种概念、约定!
关于JMM的一些同步约定:
-
线程解锁前,必须把共享变量立刻刷回主存
-
线程加锁前,必须读取主存中的最新值到共线程自己的工作内存中
-
加锁和解锁是同一把锁
线程 工作内存、主内存
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read、write操作在某些平台上允许例外)
- Read(读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用;
- load(载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中;
- Use(使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令;
- assign(赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中;
- store(存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用;
- write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中;
- lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态;
- unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定;
存在问题:
所以JMM对这8种操作给了相应的规定:
- 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
- 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
- 不允许一个线程将没有assign的数据从工作内存同步回主内存
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过assign和load操作
- 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
- 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
- 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
- 对一个变量进行unlock操作之前,必须把此变量同步回主内存
问题:程序不知道主存的值已经被修改过了,而是线程一直运行下去不会停止:
/** * @author cVzhanshi * @create 2021-09-24 15:15 */ public class JMMDemo { private static Integer number = 0; public static void main(String[] args) { new Thread(() -> { while (number == 0){ } }).start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } number = 1; System.out.println(number); } }
17、Volatile
1、保证可见性
/** * @author cVzhanshi * @create 2021-09-24 15:15 */ public class JMMDemo { // 如果不加volatile 程序会死循环 // 加了volatile是可以保证可见性的 private volatile static Integer number = 0; public static void main(String[] args) { new Thread(() -> { // 线程1 对主内存的变化不知道的 while (number == 0){ } }).start(); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } number = 1; System.out.println(number); } }
2、不保证原子性
原子性:不可分割
线程A在执行任务的时候,不能被打扰,也不能被分割,要么同时成功,要么同时失败。
/** * @author cVzhanshi * @create 2021-09-24 15:30 */ //测试不保证原子性 public class VDemo02 { //volatile 不保证原子性 private volatile static int num = 0; public static void add(){ num++; } public static void main(String[] args) { //理论上num为20000 for(int i = 1;i<=20;i++){ new Thread(() -> { for (int j = 0; j < 1000; j++) { add(); } }).start(); } while (Thread.activeCount() > 2){ // main gc 默认两个线程 Thread.yield(); } System.out.println(Thread.currentThread().getName() + " " + num); } }
num++底层(非原子性操作):
如果不加synchronized和lock,怎样保证原子性
-
使用原子类,解决原子性问题
/** * @author cVzhanshi * @create 2021-09-24 15:30 */ public class VDemo02 { //volatile 不保证原子性 但是使用原子类就能保证原子性了 private volatile static AtomicInteger num = new AtomicInteger(); public static void add(){ num.getAndIncrement(); // AtomicInteger + 1 方法,CAS } public static void main(String[] args) { //理论上num为20000 for(int i = 1;i<=20;i++){ new Thread(() -> { for (int j = 0; j < 1000; j++) { add(); } }).start(); } while (Thread.activeCount() > 2){ // main gc 默认两个线程 Thread.yield(); } System.out.println(Thread.currentThread().getName() + " " + num); } }
原子类的底层都直接和操作系统挂钩!在内存中修改值!Unsafe类是一个很特殊的存在
3、禁止指令重排
什么是指令重排?
我们写的程序,计算机并不是按照我们自己写的那样去执行的
源代码–>编译器优化重排–>指令并行也可能会重排–>内存系统也会重排–>执行
处理器在进行指令重排的时候,会考虑数据之间的依赖性!
例如:
int x=1; //1 int y=2; //2 x=x+5; //3 y=x*x; //4 //我们期望的执行顺序是 1_2_3_4 可能执行的顺序会变成2134 1324 //可不可能是 4123? 不可能的 因为结果改变了
可能造成的影响结果:其中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 |
指令重排可能会出现结果:x=2; y=1
原理探究
volatile可以避免指令重排,volatile中会加一道内存的屏障,这个内存屏障可以保证在这个屏障中的指令顺序
内存屏障:CPU指令。作用:
1、保证特定的操作的执行顺序;
2、可以保证某些变量的内存可见性(利用这些特性,就可以保证volatile实现的可见性)
小结
- volatile可以保证可见性;
- 不能保证原子性
- 由于内存屏障,可以保证避免指令重排的现象产生
内存屏障用得最多是单例模式
18、彻底玩转单例模式
饿汉式单例模式
/** * @author cVzhanshi * @create 2021-09-25 16:13 */ // 饿汉式单例 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 Hungry(){} private final static Hungry HUNGRY = new Hungry(); public static Hungry getInstance(){ return HUNGRY; } }
DCL懒汉式单例模式
-
初始版本
/** * @author cVzhanshi * @create 2021-09-26 10:22 */ public class LazyMan { // 私有化构造器 private LazyMan(){ System.out.println(Thread.currentThread().getName() + "ok"); } private static LazyMan lazyMan; public static LazyMan getInstance(){ if(lazyMan == null){ lazyMan = new LazyMan(); } return lazyMan; } public static void main(String[] args) { // 单线程下绝对正确且安全,但是在多线程下不安全 for (int i = 0; i < 10; i++) { new Thread(() -> { LazyMan.getInstance(); }).start(); } } }
多线程下不安全,就不是只有一个对象
-
加锁变成DCL懒汉式单例模式
/** * @author cVzhanshi * @create 2021-09-26 10:22 */ public class LazyMan { // 私有化构造器 private LazyMan(){ System.out.println(Thread.currentThread().getName() + "ok"); } private 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) { // 单线程下绝对正确且安全,但是在多线程下不安全 for (int i = 0; i < 10; i++) { new Thread(() -> { LazyMan.getInstance(); }).start(); } } }
运行结果实现了单例,但是也不是绝对安全的,因为lazyMan = new LazyMan();不是一个原子性操作
-
最终版的DCL单例模式
/** * @author cVzhanshi * @create 2021-09-26 10:22 */ public class LazyMan { // 私有化构造器 private LazyMan(){ System.out.println(Thread.currentThread().getName() + "ok"); } // + volatile 防止指令重排 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) { // 单线程下绝对正确且安全,但是在多线程下不安全 for (int i = 0; i < 10; i++) { new Thread(() -> { LazyMan.getInstance(); }).start(); } } }
分析:lazyMan = new LazyMan() 不是一个原子性操作
lazyMan = new LazyMan();的执行步骤
1、分配内存空间
2、执行构造方法,初始化对象
3、把这个对象指向这个空间
可能由于指令重排 把执行顺序变成 1-3-2
造成的结果:线程A还没有初始化对象,线程B获取对象是lazyMan!=null就返回对象,此时lazyMan还没有完成构造
静态内部类
//静态内部类 public class Holder { private Holder(){ } public static Holder getInstance(){ return InnerClass.holder; } public static class InnerClass{ private static final Holder holder = new Holder(); } }
单例不安全(因为反射)
-
情况1:第一个对象通过类去得到,第二个对象通过反射通过构造器造对象,破坏单例
- 代码示例:
/** * @author cVzhanshi * @create 2021-09-26 10:22 */ public class LazyMan { // 私有化构造器 private LazyMan(){ System.out.println(Thread.currentThread().getName() + "ok"); } // + volatile 防止指令重排 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 NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { // 单线程下绝对正确且安全,但是在多线程下不安全 LazyMan lazyMan = LazyMan.getInstance(); Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); LazyMan lazyMan1 = declaredConstructor.newInstance(); System.out.println(lazyMan); System.out.println(lazyMan1); } }
- 解决办法:可以在构造器中添加判断
... private LazyMan(){ if(lazyMan != null){ throw new RuntimeException("不要试图通过反射破坏单例"); } System.out.println(Thread.currentThread().getName() + "ok"); } ...//省略的代码和上面一样
-
情况二:两个对象都通过反射得到
/** * @author cVzhanshi * @create 2021-09-26 10:22 */ public class LazyMan { // 私有化构造器 private LazyMan(){ if(lazyMan != null){ throw new RuntimeException("不要试图通过反射破坏单例"); } System.out.println(Thread.currentThread().getName() + "ok"); } // + volatile 防止指令重排 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 NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { // 单线程下绝对正确且安全,但是在多线程下不安全 Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); LazyMan lazyMan = declaredConstructor.newInstance(); LazyMan lazyMan1 = declaredConstructor.newInstance(); System.out.println(lazyMan); System.out.println(lazyMan1); } }
原因:对象都通过反射得到,导致原类中的LazyMan没有被构造且一直为null,所以都能通过构造器里面的判断
解决方案:设置一个红绿灯(一个标志,非当前对象)来判断
/** * @author cVzhanshi * @create 2021-09-26 10:22 */ public class LazyMan { private static boolean cvzhanshi = false; // 私有化构造器 private LazyMan(){ synchronized (LazyMan.class){ if(cvzhanshi == false){ cvzhanshi = true; }else{ throw new RuntimeException("不要试图通过反射破坏单例"); } } System.out.println(Thread.currentThread().getName() + "ok"); } // + volatile 防止指令重排 private volatile static LazyMan lazyMan; // 双重检测锁模式的懒汉式单例 --> DCL懒汉式 public static LazyMan getInstance(){ if(lazyMan == null){ synchronized (LazyMan.class){ if(lazyMan == null){ lazyMan = new LazyMan(); // 不是一个原子性操作 /** * lazyMan = new LazyMan();的执行步骤 * 1、分配内存空间 * 2、执行构造方法,初始化对象 * 3、把这个对象指向这个空间 * 可能由于指令重排 把执行顺序变成 1-3-2 * 造成的结果:线程A还没有初始化对象,线程B获取对象是lazyMan!=null就返回对象,此时lazyMan还没有完成构造 */ } } } return lazyMan; } public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException { // 单线程下绝对正确且安全,但是在多线程下不安全 Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); declaredConstructor.setAccessible(true); LazyMan lazyMan = declaredConstructor.newInstance(); LazyMan lazyMan1 = declaredConstructor.newInstance(); System.out.println(lazyMan); System.out.println(lazyMan1); } }
-
情况三:在二的基础上那个“红绿灯”被破解了,也通过反射进行修改,进而破坏单例
... public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException { // 单线程下绝对正确且安全,但是在多线程下不安全 Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null); // 获取cvzhanshi属性 Field cvzhanshi = LazyMan.class.getDeclaredField("cvzhanshi"); cvzhanshi.setAccessible(false); declaredConstructor.setAccessible(true); LazyMan lazyMan = declaredConstructor.newInstance(); cvzhanshi.set(lazyMan,false); LazyMan lazyMan1 = declaredConstructor.newInstance(); System.out.println(lazyMan); System.out.println(lazyMan1); } ...//省略的代码和上面一样
查看newInstance方法,发现不能使用反射而破坏枚举的单例模式
尝试通过反射,破坏枚举类的单例模式
-
正常取枚举类中的对象,确实是单例模式
/** * @author cVzhanshi * @create 2021-09-26 15:10 */ public enum EnumSingle { INSTANCE; public EnumSingle getInstance(){ return INSTANCE; } } class Test{ public static void main(String[] args) { EnumSingle instance1 = EnumSingle.INSTANCE; EnumSingle instance2 = EnumSingle.INSTANCE; System.out.println(instance1); System.out.println(instance2); } }
-
通过查看枚举类编译的class文件,可以看到一个无参构造器
package cn.cvzhanshi.single; public enum EnumSingle { INSTANCE; private EnumSingle() { } public EnumSingle getInstance() { return INSTANCE; } }
-
通过反射调用构造器构造对象,破坏单例
/** * @author cVzhanshi * @create 2021-09-26 15:10 */ 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(null); declaredConstructor.setAccessible(true); EnumSingle instance2 = declaredConstructor.newInstance(); System.out.println(instance1); System.out.println(instance2); } }
结果不尽人意,报错没有空参构造器
对class文件进行反编译查看代码,发现也有空参构造器我们使用更专业的反编译工具jad.exe,查看源代码可知他是有参构造器
结论:idea骗了我们
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 }); } }
-
得知原因后继续通过反射通过构造器构造对象,破坏单例
.... 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); } } ...//省略的代码和上面一样
通过结果,我们得知枚举确实不能通过反射去改变单例模式
19、深入理解CAS
什么是CAS
CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么执行操作,如果不是就一直循环(自旋锁)
/** * @author cVzhanshi * @create 2021-09-26 15:43 */ 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, 2020)); System.out.println(atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(2021, 2020)); System.out.println(atomicInteger.get()); } }
Unsafe类
Unsafe类执行+1操作的底层
总结:
CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么执行操作,如果不是就一直循环(自旋锁)
缺点:
- 循环会耗时
- 一次性只能保证一个共享变量的原子性
- 存在ABA问题
CAS:ABA问题(狸猫换太子)
图解:
线程A虽然拿到了期望的值,但是这个期望的值已经不是最开始的值了,是B修改过的值
/** * @author cVzhanshi * @create 2021-09-26 15:43 */ 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, 2020)); System.out.println(atomicInteger.get()); System.out.println(atomicInteger.compareAndSet(2020, 2021)); System.out.println(atomicInteger.get()); // ===============期望的线程=============== System.out.println(atomicInteger.compareAndSet(2021, 6666)); System.out.println(atomicInteger.get()); } }
20、原子引用
带版本号的原子操作
解决ABA问题,对应的思想:就是使用了乐观锁~
代码示例:
/** * @author cVzhanshi * @create 2021-09-26 17:21 */ // 测试原子引用 public class CASDemo02 { 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(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicStampedReference.compareAndSet(2020, 6666, stamp, stamp + 1)); System.out.println("b2=>"+ atomicStampedReference.getStamp()); },"B").start(); } }
运行结果:是错误的
错误原因:Integer使用了对象缓存机制,默认范围是-128 ~ 127 , 推荐使用静态工厂方法valueOf获取对象实例,而不是new,因
**为valueOf使用缓存,而new 一定会创建新的对象分配新的内存空间;**代码中我们用的数太大,导致原子引用的不是同一个对象,所以修改不成功。
阿里巴巴开发手册:
解决方案:把数字改小(一搬在生产中不用使用包装类做泛型,用自定义对象如:User)
/** * @author cVzhanshi * @create 2021-09-26 17:21 */ // 测试原子引用 public class CASDemo02 { public static void main(String[] args) { // AtomicStampedReference 注意,如果泛型是一个包装类,应该注意对象的引用问题 AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1, 1); // CAS compareAndSet:比较并交换 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(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(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicStampedReference.compareAndSet(1, 6666, stamp, stamp + 1)); System.out.println("b2=>"+ atomicStampedReference.getStamp()); },"B").start(); } }
21、各种锁的理解
21.1 公平锁、非公平锁
公平锁:非常公平,不能够插队,必须先来后到
非公平锁:非常不公平,可以插队(默认是非公平锁)
// 默认非公平锁 public ReentrantLock() { sync = new NonfairSync(); } // 根据传入的参数 改变 public ReentrantLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
21.2 可重入锁
可重入锁(递归锁)
Synchronized版
/** * @author cVzhanshi * @create 2021-09-26 17:56 */ // 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"); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } }
lock版
/** * @author cVzhanshi * @create 2021-09-26 17:56 */ // Synchronized 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(); 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"); try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
同理需要A把两把锁都释放了B才能拿到锁
小结
细节问题:
lock锁必须配对,否则就会死在里面
lock版和Synchronied版的区别:
- lock版有两把锁,A线程拿到第一把的时候紧接着就拿到了第二把
- Synchronized版,只有一把锁
21.3 自旋锁
使用自旋锁自定义锁
/** * @author cVzhanshi * @create 2021-09-26 20:10 */ public class SpinlockDemo { // 泛型为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.getName()+"===> myUnlock"); atomicReference.compareAndSet(thread,null); } }
测试
/** * @author cVzhanshi * @create 2021-09-26 20:19 */ public class TestSpinLock { public static void main(String[] args) { SpinlockDemo lock = new SpinlockDemo(); new Thread(() -> { lock.myLock(); try { TimeUnit.SECONDS.sleep(3); } catch (Exception e) { e.printStackTrace(); } finally { lock.myUnLock(); } },"T1").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(() -> { lock.myLock(); try { TimeUnit.SECONDS.sleep(3); } catch (Exception e) { e.printStackTrace(); } finally { lock.myUnLock(); } },"T2").start(); } }
运行结果:
T2一定要等T1释放了锁才能拿到且释放锁,在这之前进行自旋等待
21.4 死锁
死锁是什么
死锁测试
/** * @author cVzhanshi * @create 2021-09-26 20:34 */ 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; } @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); } } } }
死锁排查,解决问题
-
使用
jsp -l
定位进程号 -
使用
jstack 进程号
查看堆栈信息找到死锁
这篇关于并发编程JUC(下)的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-29怎么去掉UniApp中字符串的空格?-icode9专业技术文章分享
- 2024-11-29Warning: Cannot modify header information - headers already sent by 报错信息是什么?-icode9专业技术文章分享
- 2024-11-29Excel中实现拖动排序的简易教程
- 2024-11-29如何在Excel中使用拖动排序功能
- 2024-11-28阿里云 ECS课程:新手入门教程
- 2024-11-27Excel中实现拖动排序的简单教程
- 2024-11-27Rocket消息队列资料:新手入门指南
- 2024-11-27rocket消息队资料详解与入门指南
- 2024-11-27RocketMQ底层原理资料详解入门教程
- 2024-11-27RocketMQ项目开发资料:新手入门教程