📣 📣 📣 📢📢📢
☀️☀️你好啊!小伙伴,我是小冷。是一个兴趣驱动自学练习两年半的的Java工程师。
📒 一位十分喜欢将知识分享出来的Java博主⭐️⭐️⭐️,擅长使用Java技术开发web项目和工具
📒 文章内容丰富:覆盖大部分java必学技术栈,前端,计算机基础,容器等方面的文章
📒 如果你也对Java感兴趣,关注小冷吧,一起探索Java技术的生态与进步,一起讨论Java技术的使用与学习
✏️高质量技术专栏专栏链接: 微服务, netty, 单点登录, SSM , SpringCloudAlibaba等
😝公众号😝 : 想全栈的小冷,分享一些技术上的文章,以及解决问题的经验
⏩ 当前专栏: JUC系列
JMM
谈谈队 Volatile的理解
Volatile 是 java 虚拟机的轻量级同步机制
- 保证可见性
- 不保证原子性
- 禁止指令重排
什么是Jmm
JMM就是Java内存模型(java memory model) Java内存模型规定所有的变量都存储在主内存中,包括实例变量,静态变量,但是不包括局部变量和方法参数。每个线程都有自己的工作内存,线程的工作内存保存了该线程用到的变量和主内存的副本拷贝,线程对变量的操作都在工作内存中进行。线程不能直接读写主内存中的变量。
jmm的一些同步约定
- 线程解锁前 必须把共享变量 立刻刷新回主缓存
- 线程加锁 前,必须读取主内存中的最新值到工作中
- 加锁和解锁必须是同一把锁
线程 : 工作内存和 主内存
Jmm中的八种操作:
- lock(锁定),作用于主内存中的变量,把变量标识为线程独占的状态。
- read(读取),作用于主内存的变量,把变量的值从主内存传输到线程的工作内存中,以便下一步的load操作使用。
- load(加载),作用于工作内存的变量,把read操作主存的变量放入到工作内存的变量副本中。
- use(使用),作用于工作内存的变量,把工作内存中的变量传输到执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作。
- assign(赋值),作用于工作内存的变量,它把一个从执行引擎中接受到的值赋值给工作内存的变量副本中,每当虚拟机遇到一个给变量赋值的字节码指令时将会执行这个操作。
- store(存储),作用于工作内存的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用。
- write(写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中。
- unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
JMM对8种内存交互操作制定的规则:
- 不允许read、load、store、write操作之一单独出现,也就是read操作后必须load,store操作后必须write。
- 不允许线程丢弃他最近的assign操作,即工作内存中的变量数据改变了之后,必须告知主存。
- 不允许线程将没有assign的数据从工作内存同步到主内存。
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是对变量实施use、store操作之前,必须经过load和assign操作。
- 一个变量同一时间只能有一个线程对其进行lock操作。多次lock之后,必须执行相同次数unlock才可以解锁。
- 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值。在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值。
- 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量。
- 一个线程对一个变量进行unlock操作之前,必须先把此变量同步回主内存。
Volatile
验证 这个关键字的特性
- 保证可见性
- 不保证原子性
- 禁止指令重排
1 可见性
//volatile 之后 就会循环停止了,验证了特性 1 可见性
private volatile static int num = 0;
public static void main(String[] args) {
//此时循环 没有结束 为什么? 因为线程队主内存的变化并不知道
new Thread(() -> {
while (num == 0) {
}
}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
}
2 不保证原子性
使用 synchronized 关键字 保证原子性 结果为 20000
private static int num = 0;
public synchronized static void add() {
num++;
}
public static void main(String[] args) {
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + num);
}
使用 Volatile 不保证原子性 17824 每次出来的都不一样,可能是两万 概率不大
private volatile static int num = 0;
public static void add() {
num++;
}
public static void main(String[] args) {
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + num);
}
问题 如果不上锁 也不用 关键字 ,我们怎么保证原子性
使用JUC 包中的 原子类 来解决 原子性问题
比如 int : 替换成 原子类 AtomicInteger
private volatile static AtomicInteger num = new AtomicInteger(0);
public static void add() {
//num++;
// incr 自增
num.getAndIncrement();
}
public static void main(String[] args) {
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + num);
}
保证了原子操作 输出20000
这些原子类的底层直接和操作系统挂钩,在内存中修改值,Unsafe 类是一个中很特殊的存在
指令重排
什么是 指令重排: 我们写的程序 计算机不一定会按照我们写的顺序来执行
源代码 -> 编译器优化重排 -> 指令并行可能会重排->内存系统也会重排-->执行
==处理器再进行指令重排的时候 考虑数据之间的依赖性==
int x = 1;// 1
int y = 2;// 2
x = x + 5;// 3
y = x * x;// 4
我们期待的是 1234, 可能执行的时候变成了 2134 1324
但是不可能是 4123
可能 造成影响最后的执行结果 a b x y 默认值都是 0
正常的结果 : x=0;y=0; 可能由于指令重排
指令重排 导致的奇怪结构 = x=2 y=1
volatile 可以避免指令重排
内存屏障 CPU 指令 作用:
- 保证特定操作的执行顺序
- 可以保证某些变量的内存可见性
单例模式
饿汉式
public class Hungryemo {
private Hungryemo() {
}
private final static Hungryemo HUNGRYEMO = new Hungryemo();
public static Hungryemo getIstance() {
return HUNGRYEMO;
}
}
DCL懒汉式
package single;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
/**
* @projectName: JUC
* @package: single
* @className: LazyMan
* @author: 冷环渊 doomwatcher
* @description: TODO
* @date: 2022/3/4 17:09
* @version: 1.0
*/
public class LazyMan {
private static boolean flag = true;
private LazyMan() {
synchronized (LazyMan.class) {
if (flag == false) {
flag = true;
} else {
throw new RuntimeException("不要试图用反射破坏单例模式");
}
if (lazyMan != null) {
throw new RuntimeException("不要试图用反射破坏单例模式");
}
}
}
//必须 加上 volatile 避免 指令重排
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 就会以为已经完成构造,指向就是null 可能会空指针
* */
}
}
}
return lazyMan;
}
//但是会有并发问题
public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<EnumSngle> field = EnumSngle.class.getDeclaredConstructor(String.class, int.class);
field.setAccessible(true);
//LazyMan lazyMan = field.newInstance();
//LazyMan lazyMan1 = field.newInstance();
EnumSngle lazyMan = field.newInstance();
EnumSngle lazyMan1 = field.newInstance();
System.out.println(lazyMan);
System.out.println(lazyMan1);
}
}
静态内部类
public class Holder {
private Holder() {
}
public static Holder getInstance() {
return innerClass.holder;
}
public static class innerClass {
private final static Holder holder = new Holder();
}
}
枚举
//enum 是什么? 本身也是一个 Class 类
public enum EnumSngle {
INSTANCE;
private EnumSngle() {
}
public EnumSngle getInstance() {
return INSTANCE;
}
}
class test{
//但是会有并发问题
public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<EnumSngle> field = EnumSngle.class.getDeclaredConstructor(String.class, int.class);
field.setAccessible(true);
//LazyMan lazyMan = field.newInstance();
//LazyMan lazyMan1 = field.newInstance();
EnumSngle lazyMan = field.newInstance();
EnumSngle lazyMan1 = field.newInstance();
System.out.println(lazyMan);
System.out.println(lazyMan1);
}
}