一.:前提说明 + 要求(1~1)
二.:Java 基础:
三.:JUC 多线程 及 高并发(2~)
( java.util.concurrent ) --> ( Java并发包 )
1_1:volatile__介绍:
1)volatile 是 Java 虚拟机提供的一种,轻量级的同步机制:
(可以理解为:乞丐版的 synchronized)
(三大特性)
-1:保证 可见性:
-2:不保证 原子性:
-3:禁止 指令重排序:
1_2:volatile__JMM 你谈谈:(Java 内存模型)
(Java Memory Model)(线程安全性,获得保证)
1)JMM—可见性:(Java 内存模型)
-1:JMM 解释:
-2:JMM 关于同步的规定:
1:线程加锁前:必须读取主内存的最新值,到自己的工作内存。
2:线程解锁前:必须把共享变量的值,刷新回主内存。
3:加锁、解锁 是同一把锁。
-3:可见性问题产生:
-4:课件详解:
-5:JMM 中,(主内存 & 自己工作内存)交互:
1:主内存:硬件,8G。
2:主内存中,有 student 对象:age = 25。
3:三个线程同时操作:分别取出主内存数据(age = 25),变量拷贝到自己线程的工作内存中,各自做计算操作。
4:t1 线程,对 age 进行改变,age = 37,然后将 37 写回给主内存。
5:但是,t1 改变,主内存改变,t2、t3 线程,还不知道改变。
6:要将 主内存的改变,即时通知到其他线程,及时通知的情况,就叫为 JMM 内存模型中的 可见性。
-6:可见性代码验证:
/**
* @author zhangxudong@chunyu.me
* @date 2022/4/15 8:40 上午
*/
public class MyVolatile {
public static void main(String[] args) {
MyData myData = new MyData();
new Thread(() -> {
System.out.println("开始修改变量值");
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
}
myData.addTo60();
System.out.println("变量值修改完毕:number = " + myData.number);
}).start();
new Thread(() -> {
while (myData.number == 0) {
// 如果 number,没有添加 volatile 关键字,
// 则此线程,不会看到数值,已经被修改为 60,
// 会一直循环下去。
}
System.out.println("检测到:a==60,返回");
}).start();
}
}
class MyData {
int number = 0; // 改变不可见
// 增强了:主内存 和 个个线程之间的可见性。
// volatile int number = 0;
public void addTo60() {
this.number = 60;
}
}
2)JMM—原子性:
-1:原子性指的是什么:
1:不可分割,完整性。
2:也即:某个线程正在做某个具体业务时,中间不可以被加塞或者被分割。
3:需要整体完整,要么同时成功,要么同时失败。
-2:volatile 不保证原子性,案例演示:
public class VolatileAtomic {
public static void main(String[] args) throws InterruptedException {
MyDataAtomic atomic = new MyDataAtomic();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
atomic.addOne();
}
}).start();
}
// 所有线程计算停止后,才会取值展示
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(atomic.number); // 18888
}
}
class MyDataAtomic {
volatile int number = 0;
public void addOne() {
// 在多线程下,线程不安全
number++;
}
}
-3:解释:Volatile 不保证原子性:(number++;)
(从 虚拟机认识的,字节码文件分析)
-4:解决:不保证原子性问题:(保证:可见性 + 原子性)
(不建议加 :synchronized,杀鸡用牛刀)
/**
* @author zhangxudong@chunyu.me
* @date 2022/4/15 10:24 上午
*/
public class VolatileAtomic {
public static void main(String[] args) throws InterruptedException {
MyDataAtomic atomic = new MyDataAtomic();
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int j = 0; j < 1000; j++) {
atomic.addOne(); //n++
atomic.addatomic(); // 原子 ++
}
}).start();
}
// 所有线程计算停止后,才会取值展示
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println(atomic.number); // 18888
System.out.println(atomic.atomicIntegerNumber); // 20000
}
}
class MyDataAtomic {
volatile int number = 0;
public void addOne() {
number++;
}
AtomicInteger atomicIntegerNumber = new AtomicInteger(0);
public void addatomic() {
atomicIntegerNumber.incrementAndGet();
}
}
3)JMM—可见性、有序性:(指令重排)
-1:有序性说明:
-2:重排:例(1)
-3:重排:例(2)
-4:禁止指令重排序原理:
1_3:volatile__哪些地方使用过:
1)(单例模式,在多线程环境下,可能存在的安全问题):
-1:JUC 包里面,大规模使用。
2)单例模式:DCL 代码:(双重检查锁机制)
-1:但是:多线程下存在问题:
1:为了性能和效果,底层有指令重排序。
2:没有控制好指令重排序,就可能会出现异常情况。
public class chongpai1 {
public static void main(String[] args) {
for (int ni = 0; ni < 1000; ni++) {
new Thread(() -> {
// 1000 个线程,调用
SingletonDemo.getSingletonDemo();
}).start();
}
}
}
class SingletonDemo {
private static SingletonDemo singletonDemo = null;
private SingletonDemo() {
System.out.println("使用构造方法");
}
public static SingletonDemo getSingletonDemo() {
if (singletonDemo == null) {
synchronized (SingletonDemo.class) {
if (singletonDemo == null) {
singletonDemo = new SingletonDemo();
}
}
}
return singletonDemo;
}
}
3)单例模式:volatile 代码:(上面:DCL 模式的改进)
-1:DCL 机制,不一定安全,因为有指令重排序的存在,加入 volitaile,可以禁止指令重排序。
-2:DCL 分析:
2:CAS 你知道吗:( compareAndSwap )
1)比较并交换( compareAndSwap ):代码案例:
AtomicInteger atomicInteger = new AtomicInteger(5);
/**
* 相当于 i++
* 底层原理就是 CAS
*/
atomicInteger.getAndIncrement();
// 比较并交换
atomicInteger.compareAndSet(5, 100); // 修改成功
atomicInteger.compareAndSet(6, 200); // 修改失败
// 修改成功,输出100
System.out.println(atomicInteger.get());
2)CAS 底层原理?Unsafe类的理解:
-1:入门解释:(比较并交换)
(如果期望值与主内存的值一样,才会修改成功)
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
// 输出 true
System.out.println(atomicInteger.compareAndSet(5, 100));
// 输出 false
System.out.println(atomicInteger.compareAndSet(5, 200));
// 最终值:输出100
System.out.println(atomicInteger.get());
}
-2:AtomicInteger 底层代码:
【实现原子性原理:(CAS思想(自旋) + UnSafe 类 )】
UnSafe 类的路径(娘胎里就带的类)::/Library/Java/JavaVirtualMachines/zulu-8.jdk/Contents/Home/jre/lib/rt.jar!/sun/misc/Unsafe.class
-3:UnSafe 类详解:
-4:CAS 是什么:(自旋思想)
1:unsafe.getAndAndInt():翻看底层代码。
2:底层汇编语言:
3:简单版 小总结:
3)CAS 缺点:
-1:如果:CAS 一直失败,会一直循环进行尝试。如果 CAS 一直不成功,会给 CPU 带来很大的开销:
-2:只能保证,一个共享变量的原子操作:多个变量,CAS 就不能保证操作原子性,需要加锁来保证。
-3:会有 ABA 问题:
3:AutomicInteger 的 ABA 问题,,原子更新引用知道吗:
1)ABA 问题产生原因:(狸猫换太子)
(只比较了开头和尾巴,没有管中间的操作。中间可能被操作过。)
/**
* ABA 问题的产生
*/
public class MyABA {
public static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
public static void main(String[] args) {
// 100 -> 101 -> 100
new Thread(() -> {
atomicReference.compareAndSet(100, 101);
atomicReference.compareAndSet(101, 100);
}, "t1").start();
// 100 -> 1029,虽然 100 这个值,被其他线程变动,
// 但比较 100 = 100,仍然会修改成功
new Thread(() -> {
// 暂停 1s,保证上面的 t1 线程完成 ABA 运算
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
atomicReference.compareAndSet(100, 2019);
System.out.println(atomicReference.get());
}, "t2").start();
}
}
2)原子引用:
public static void main(String[] args) {
User z3 = new User("z3", 15);
User l4 = new User("l4", 17);
AtomicReference<User> userAtomicReference = new AtomicReference<>();
userAtomicReference.set(z3);
userAtomicReference.compareAndSet(z3, l4);
System.out.println(userAtomicReference.get());
// 此时主内存值,已经被替换为 l4,比较并交换失败。
userAtomicReference.compareAndSet(z3, l4);
System.out.println(userAtomicReference.get());
}
3)版本号 原子引用:(解决 ABA 问题)
/**
* ABA 问题的解决
*/
public class MyABA22 {
public static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
public static void main(String[] args) {
new Thread(() -> {
// 获取第一次版本号
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + ":第一次版本号:" + stamp);// t1:第一次版本号:1
// 暂停 1s,保证,t1,t2 拿到的第一次版本号相同。
try {
TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) {
}
// ABA 问题产生
atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + ":第二次版本号:" + atomicStampedReference.getStamp());// t1:第二次版本号:2
atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp(