前端模拟面试:给你真实的求职体验和面试经验内附资料

#1

download:前端模拟面试:给你真实的求职体验和面试经验内附资料

Happens-Before规则详解
对应 Java 程序员来说,了解 Happens-before 是了解 JMM 的关键。这个准绳十分重要,它是判别数据能否存在竞争,线程能否平安的十分有用的手腕。依赖这个准绳,我们能够经过几条简单规则一并处理并发环境下两个操作之间能否可能存在抵触的一切问题,而不需求堕入 Java 内存模型苦涩难懂的定义之中。
JMM的设计
如今就来看看“先行发作”准绳指的是什么。先行发作是 Java 内存模型中定义的两项操作之间的偏序关系,比方说操作A先行发作于操作B,其实就是说在发作操作B之前,操作A产生的影响能被操作B察看到,“影响”包括修正了内存中共享变量的值、发送了音讯、调用了办法等。这句话不难了解,但它意味着什么呢?我们经过一个简单的案例来停止演示。
double pi = 3.14; // A
double r = 1.0; // B
double area = pi * r * r; // C
复制代码
上述代码用来计算圆的面积,存在3个 happens-before 关系,如下。

A happens-before B
B happens-before C
A happens-before C

在3个 happens-before 关系中,2和3是必需的,但1是不用要的。因而,JMM 把 happens-before 请求制止的重排序分为了下面两类。

会改动程序执行结果的重排序。
不会改动程序执行结果的重排序。

JMM 对这两种不同性质的重排序,采取了不同的战略,如下。

关于会改动程序执行结果的重排序,JMM请求编译器和处置器必需制止这种重排序。
关于不会改动程序执行结果的重排序,JMM对编译器和处置器不做请求(JMM允许这种重排序)。

综合来看,JMM 其实是在遵照一个根本准绳:只需不改动程序的执行结果(指的是单线程程序和正确同步的多线程程序),编译器和处置器怎样优化都行。例如,假如编译器经过细致的剖析后,认定一个锁只会被单个线程访问,那么这个锁能够被消弭,我们之前学习 JIT 编译器逃逸剖析时有提到。
Happens-Before 规则
下面是 Java 内存模型下一些“自然的”先行发作关系, 这些先行发作关系无须任何同步器辅佐就曾经存在, 能够在编码中直接运用。 假如两个操作之间的关系不在此列, 并且无法从下列规则推导出来, 则它们就没有次第性保证, 虚拟机能够对它们随意地停止重排序。
1、程序次序规则:在一个线程中,前面的操作 Happens-Before 于后续的恣意操作。
2、volatile变量规则:对一个 volatile 变量的写操作 Happens-Before 于对这个 volatile 变量的读操作。
3、传送性规则:A Happens-Before B,B Happens-Before C,那么 A Happens-Before C。
4、管程锁定规则:synchronized 是 Java 对管程的完成,隐式加锁、释放锁,对一个锁的解锁 Happens-Before 于后续对这个锁的加锁。

关于管程的引见:在操作系统中,管程的定义如下: 管程是由一组数据以及定义在这组数据之上的对该组数据操作的操作组成的软件模块,称之为管程。 根本特性: 1. 部分于管程的数据只能被部分于管程内的过程所访问。 2. 一个进程只要经过调用管程内的过程才干进入管程访问共享数据 3. 每次仅允许一个进程在管程中执行某个内部过程。 留意:由于管程是一个言语的成分,所以管程的互斥访问完整由编译程序在编译时自动添加,无需程序员关注。

而在 Java 中,管程指的就是 synchronized,synchronized 是 Java 里对管程的完成。
管程中的锁在 Java 里是隐式完成的,例如下面的代码,在进入同步块之前,会自动加锁,而在代码块执行完会自动释放锁,加锁以及释放锁都是编译器帮我们完成的。
synchronized (this) { //此处自动加锁
// x是共享变量,初始值=10
if (this.x < 12) {
this.x = 12;
}
} //此处自动解锁
复制代码
5、线程启动规则:Thread 对象的 start()办法先行发作于此线程的每一个动作。
主线程A启动子线程B后,子线程的 start()操作 Happens-Before于子操作中的恣意操作,即子线程 B 可以看到主线程在启动子线程 B 前的操作。
Thread B = new Thread(()->{
// 主线程调用B.start()之前
// 一切对共享变量的修正,此处皆可见
// 此例中,var==77
});
// 此处对共享变量var修正
var = 77;
// 主线程启动子线程
B.start();
复制代码
在上述代码中,main 线程启动子线程B后,B线程的 start()操作 Happens-Before于B线程操作中的恣意操作,即线程 B 可以看到主线程在启动线程 B 前的操作。
6、线程终止规则:线程中的一切操作都先行发作于对此线程的终止检测,我们能够经过 Thread.join()办法完毕、Thread.isAlive()的返回值等手腕检测到线程曾经终止执行。
Thread B = new Thread(()->{
// 此处对共享变量var修正
var = 66;
});
// 例如此处对共享变量修正,
// 则这个修正结果对线程B可见
// 主线程启动子线程
B.start();
B.join()
// 子线程一切对共享变量的修正
// 在主线程调用B.join()之后皆可见
// 此例中,var==66
复制代码
7、线程中缀规则:对线程 interrupt()办法的调用先行发作于被中缀线程的代码检测到中缀事情的发作,能够经过 Thread.interrupted()办法检测到能否有中缀发作。
public class InterruptedSleepingTest {

public static void main(String[] args) throws InterruptedException {
InterruptedSleepingThread thread = new InterruptedSleepingThread();
thread.start();

// 10s后执行中缀操作
Thread.sleep(10000);
thread.interrupt();

}
}

class InterruptedSleepingThread extends Thread {

@Override
public void run() {
doAPseudoHeavyWeightJob();
}

private void doAPseudoHeavyWeightJob() {
for (int i = 0; i < Integer.MAX_VALUE; i++) {
// You are kidding me
System.out.println(i + " " + i * 2);
// Let me sleep
if (Thread.currentThread().isInterrupted()) {
System.out.println(“Thread interrupted\n Exiting…”);
break;
} else {
sleepBabySleep();
}
}
}

protected void sleepBabySleep() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
//当主线程中的interrupt办法执行之后,才会抛出
Thread.currentThread().interrupt();
}
}
}
复制代码
执行结果为:
0 0
1 2
2 4
3 6
4 8
5 10
6 12
7 14
8 16
9 18
10 20
Thread interrupted
Exiting…
复制代码
关于线程 interrupt 办法的细致解说,能够参考这篇文章。
8、对象终结规则:一个对象的初始化完成(结构函数执行完毕)先行发作于它的 finalize()办法的开端。
public class ObjectHappensTest {

public int num;
public String name;

public ObjectHappensTest(int num, String name) {
System.out.println(“能够屡次执行结构办法”);
this.num = num;
this.name = name;
}

@Override
protected void finalize() throws Throwable {
System.out.println(“进入finalize办法,只会执行一次”);
super.finalize();
}

public static void main(String[] args) throws InterruptedException {
ObjectHappensTest obj;
// obj = new ObjectHappensTest(30, “constructor”);
obj = null;
System.gc();

Thread.sleep(2000);

}
}
复制代码
执行上述代码,什么也没有输出;假如取消注释,则会打印如下结果:
能够屡次执行结构办法
进入finalize办法,只会执行一次

1 Like
#2

:100: