Ending
Tip:由于文章篇幅有限制,下面还有20个关于MySQL的问题,我都复盘整理成一份pdf文档了,后面的内容我就把剩下的问题的目录展示给大家看一下
如果觉得有帮助不妨【转发+点赞+关注】支持我,后续会为大家带来更多的技术类文章以及学习类文章!(阿里对MySQL底层实现以及索引实现问的很多)
吃透后这份pdf,你同样可以跟面试官侃侃而谈MySQL。其实像阿里p7岗位的需求也没那么难(但也不简单),扎实的Java基础+无短板知识面+对某几个开源技术有深度学习+阅读过源码+算法刷题,这一套下来p7岗差不多没什么问题,还是希望大家都能拿到高薪offer吧。
wait —> Object
sleep --> Thread
-
关于锁的释放
wait 会释放锁,sleep休眠了,抱着锁睡觉,不会释放
-
使用的范围不同
wait必须在同步代码块中
sleep可以在任何地方休眠
================================================================================
多线程访问共享变量,造成线程不安全,最后的数值不对
package com.wanshi.cvolatile;
public class VDemo {
private static int num = 0;
public static void add() {
num++;
}
public static void main(String[] args) {
//理论上应该为20000
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 1; j <= 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println("执行结果:"+num);
}
}
执行结果
为什么出现这种问题?
是由于多个线程访问同一个变量,第一个值为0,加了1,第二个又迅速的访问,发现读取到的值依旧是0,没有获取到最新值,导致读取到的永远是旧值,这就造成了 这种问题,正常的数字应该是20000,然而少了很多。
要想解决这种问题,那就要先了解Java内存模型了。(简称JMM)
=============================================================================
JMM(Java Memory Model):Java 内存模型,是 Java 虚拟机规范中所定义的一种内存模型,Java 内存模型是标准化的,屏蔽掉了底层不同计算机的区别。也就是说,JMM 是 JVM 中定义的一种并发编程的底层模型机制。
JMM的约定
-
线程解锁前,必须把共享变量立刻刷回主存
-
线程加锁前,必须读取主存中的最新值到工作内存中
-
加锁和解锁是同一把锁
线程 工作内存 主存
多线程情况下修改主存的值
存在的问题:
线程B修改了值,线程A不能及时可见
关于主内存与工作内存之间的具体交互协议,即一个变量如何从主内存拷贝到工作内存、如何从工作内存同步到主内存之间的实现细节,Java内存模型定义了以下
八种操作
来完成:
-
lock(锁定):作用于主内存的变量,把一个变量标识为一条线程独占状态。
-
unlock(解锁):作用于主内存变量,把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定。
-
read(读取):作用于主内存变量,把一个变量值从主内存传输到线程的工作内存中,以便随后的load动作使用
-
load(载入):作用于工作内存的变量,它把read操作从主内存中得到的变量值放入工作内存的变量副本中。
-
use(使用):作用于工作内存的变量,把工作内存中的一个变量值传递给执行引擎,每当虚拟机遇到一个需要使用变量的值的字节码指令时将会执行这个操作。
-
assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收到的值赋值给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作。
-
store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送到主内存中,以便随后的write的操作。
-
write(写入):作用于主内存的变量,它把store操作从工作内存中一个变量的值传送到主内存的变量中。
Java内存模型还规定了在执行上述八种基本操作时,必须满足如下规则:
-
如果要把一个变量从主内存中复制到工作内存,就需要按顺寻地执行read和load操作, 如果把变量从工作内存中同步回主内存中,就要按顺序地执行store和write操作。但Java内存模型只要求上述操作必须按顺序执行,而没有保证必须是连续执行。
-
不允许read和load、store和write操作之一单独出现
-
不允许一个线程丢弃它的最近assign的操作,即变量在工作内存中改变了之后必须同步到主内存中。
-
不允许一个线程无原因地(没有发生过任何assign操作)把数据从工作内存同步回主内存中。
-
一个新的变量只能在主内存中诞生,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量。即就是对一个变量实施use和store操作之前,必须先执行过了assign和load操作。
-
一个变量在同一时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁。lock和unlock必须成对出现
-
如果对一个变量执行lock操作,将会清空工作内存中此变量的值,在执行引擎使用这个变量前需要重新执行load或assign操作初始化变量的值
-
如果一个变量事先没有被lock操作锁定,则不允许对它执行unlock操作;也不允许去unlock一个被其他线程锁定的变量。
-
对一个变量执行unlock操作之前,必须先把此变量同步到主内存中(执行store和write操作)。
由于未加任何保证可见性的操作,所以多线程下改变主存的值会造成不及时行,导致后面的线程拿的永远是第一个值,这样就会造成指令重排!
=========================================================================================
保证程序的可见性
package com.wanshi.cvolatile;
import java.util.concurrent.TimeUnit;
public class JMMDemo {
// 不加 volatile 程序可能会死循环
// 加上 volatile 可以保证可见性
private volatile static int num = 0;
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
while (num == 0) {
}
}).start();
TimeUnit.SECONDS.sleep(1);
num = 1;
System.out.println(num);
}
}
运行结果为 1
不保证原子性
原子性:不可分割
线程A执行的任务的时候,不能被打扰的,也不能被分割,要么同时成功,要么同时失败
package com.wanshi.cvolatile;
public class VDemo {
private volatile static int num = 0;
public static void add() {
num++;
}
public static void main(String[] args) {
//理论上应该为20000
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
for (int j = 1; j <= 1000; j++) {
add();
}
}).start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println("执行结果:"+num);
}
}
加上 synchronized 和 Lock来保证原子性
synchronized 保证原子性
package com.wanshi.cvolatile;
public class VDemo {
private volatile static int num = 0;
public static void add() {
num++;
}
public static void main(String[] args) {
//理论上应该为20000
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
//加锁来保证原子性,锁的是当前线程.class
synchronized (Thread.class) {
for (int j = 1; j <= 1000; j++) {
add();
}
}
}).start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println("执行结果:"+num);
}
}
Lock保证原子性
package com.wanshi.cvolatile;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class VDemo {
private volatile static int num = 0;
public static void add() {
num++;
}
// Lock 锁,默认为非公平锁
private static Lock lock = new ReentrantLock();
public static void main(String[] args) {
//理论上应该为20000
for (int i = 1; i <= 20; i++) {
new Thread(() -> {
try {
//上锁,注意,再此必须try/catch 防止程序出现异常,从而造成死锁问题
lock.lock();
for (int j = 1; j <= 1000; j++) {
add();
}
} catch (Exception e) {
} finally {
// 解锁
lock.unlock();
}
}).start();
}
while (Thread.activeCount() > 2) {
Thread.yield();
}
System.out.println("执行结果:"+num);
}
}
执行结果均为:20000
不加 synchronized 或者 Lock怎么保证原子性
采用 原子类的方式来保证原子性
package com.wanshi.cvolatile;
import java.util.concurrent.atomic.AtomicInteger;
public class VDemo {
//原子类的 AtumicInteger
private volatile static AtomicInteger num = new AtomicInteger();
public static void add() {
//+1 方法 底层使用CAS
num.getAndIncrement();
### 最后
> 由于细节内容实在太多了,为了不影响文章的观赏性,只截出了一部分知识点大致的介绍一下,每个小节点里面都有更细化的内容!

**小编准备了一份Java进阶学习路线图(Xmind)以及来年金三银四必备的一份《Java面试必备指南》**

> **本文已被[CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】](https://bbs.youkuaiyun.com/forums/4f45ff00ff254613a03fab5e56a57acb)收录**
**[需要这份系统化的资料的朋友,可以点击这里获取](https://bbs.youkuaiyun.com/forums/4f45ff00ff254613a03fab5e56a57acb)**
ger
private volatile static AtomicInteger num = new AtomicInteger();
public static void add() {
//+1 方法 底层使用CAS
num.getAndIncrement();
### 最后
> 由于细节内容实在太多了,为了不影响文章的观赏性,只截出了一部分知识点大致的介绍一下,每个小节点里面都有更细化的内容!
[外链图片转存中...(img-ZFQRp82G-1715654869265)]
**小编准备了一份Java进阶学习路线图(Xmind)以及来年金三银四必备的一份《Java面试必备指南》**
[外链图片转存中...(img-1W8koK3H-1715654869266)]
> **本文已被[CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】](https://bbs.youkuaiyun.com/forums/4f45ff00ff254613a03fab5e56a57acb)收录**
**[需要这份系统化的资料的朋友,可以点击这里获取](https://bbs.youkuaiyun.com/forums/4f45ff00ff254613a03fab5e56a57acb)**