Commit fccac5e1 by 仲光辉

init: project Initialization

parents
# Created by .ignore support plugin (hsz.mobi)
### Java template
# Compiled class file
*.class
# Log file
*.log
# BlueJ files
*.ctxt
# Mobile Tools for Java (J2ME)
.mtj.tmp/
# Package Files #
*.war
*.nar
*.ear
*.zip
*.tar.gz
*.rar
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
.idea
*.iml
build
.gradle
// java,IntelliJ IDEA 插件
plugins {
id 'java'
id 'idea'
}
group 'cn.dankal.share'
description 'dankal-share-volatile'
version '1.0.0.RELEASE'
// 依赖仓库配置
repositories {
// GRADLE_USER_HOME : maven 本地仓库地址
mavenLocal()
maven {
url 'https://maven.aliyun.com/repository/public/'
}
maven {
url 'https://maven.aliyun.com/repository/spring/'
}
maven {
url 'https://maven.aliyun.com/repository/gradle-plugin'
}
maven {
url 'https://maven.aliyun.com/repository/spring-plugin'
}
mavenCentral()
}
// 编译字符集
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
// 使用的 java 版本
java.sourceCompatibility = JavaVersion.VERSION_1_8
java.targetCompatibility = JavaVersion.VERSION_1_8
// 项目依赖
dependencies {
implementation 'cn.hutool:hutool-core:5.5.4'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.6.0'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine'
}
// 执行测试任务
test {
useJUnitPlatform()
}
\ No newline at end of file
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://res.mercymodest.com/gradle-6.7.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
rootProject.name = 'dankal-share-volatile'
package cn.dankal.share;
/**
* DclSingletonTest
* <br/>
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020/12/29
* @copyright 蛋壳创意科技 - www.dankal.cn
*/
public class DclSingletonTest {
public static void main(String[] args) {
SingletonInstance singletonInstance = SingletonInstance.getInstance();
}
}
class SingletonInstance {
private static volatile SingletonInstance singletonInstance;
private SingletonInstance() {
}
public static SingletonInstance getInstance() {
if (null == singletonInstance) {
synchronized (SingletonInstance.class) {
if (null == singletonInstance) {
singletonInstance = new SingletonInstance();
}
}
}
return singletonInstance;
}
}
package cn.dankal.share;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* VolatileKeywordTest
* <br/>
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020/12/29
* @copyright 蛋壳创意科技 - www.dankal.cn
*/
public class VolatileKeywordTest {
public static void main(String[] args) throws InterruptedException {
final DataOperator dataOperator = new DataOperator();
ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor("test volatile with atomic -- ");
// 计数扣减 保证 main 线程 在 20 个子线程之后执行
final CountDownLatch countDownLatch = new CountDownLatch(20);
final int threadCount = 20;
for (int i = 0; i < threadCount; i++) {
threadPoolExecutor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " do incr by atomic");
final int executeCount = 1000;
for (int j = 0; j < executeCount; j++) {
dataOperator.atomicIncrNum();
}
countDownLatch.countDown();
});
}
// 等待 20 个线程执行完成
countDownLatch.await();
System.out.println("the executor result: " + dataOperator.atomicInteger2Num.get());
threadPoolExecutor.shutdown();
}
/**
* 测试 Volatile 关键字无法保证原子性
*
* @param dataOperator DataOperator
* @throws InterruptedException
*/
private static void testVolatileNonAtomic(DataOperator dataOperator) throws InterruptedException {
ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor("test volatile non atomic -- ");
// 计数扣减 保证 main 线程 在 20 个子线程之后执行
final CountDownLatch countDownLatch = new CountDownLatch(20);
final int threadCount = 20;
for (int i = 0; i < threadCount; i++) {
threadPoolExecutor.execute(() -> {
final int executeCount = 1000;
for (int j = 0; j < executeCount; j++) {
dataOperator.incrNum();
}
countDownLatch.countDown();
});
}
// 等待 20 个线程执行完成
countDownLatch.await();
System.out.println("the executor result: " + dataOperator.num);
threadPoolExecutor.shutdown();
}
/**
* 获取 线程池
*
* @param threadNamePrefix 线程池线程名称前缀
* @return ThreadPoolExecutor
*/
private static ThreadPoolExecutor getThreadPoolExecutor(String threadNamePrefix) {
return new ThreadPoolExecutor(
20,
35,
10,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1 << 10),
runnable -> {
Thread thread = new Thread(runnable);
thread.setName(threadNamePrefix + thread.getId());
return thread;
}
);
}
/**
* 测试 Volatile 关键字 的 可见性
*
* @param dataOperator DataOperator
*/
private static void testVolatileVisibility(DataOperator dataOperator) {
new Thread(() -> {
try {
// 休眠 3 秒
TimeUnit.SECONDS.sleep(3);
dataOperator.setNum2New(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " num :" + dataOperator.num);
}, "edit num thread").start();
while (dataOperator.num == 0) {
// 阻塞 main 线程
}
System.out.println(Thread.currentThread().getName() + " the num is " + dataOperator.num + " not zero");
}
}
class DataOperator {
volatile int num = 0;
public void setNum2New(int newNum) {
this.num = newNum;
}
public void incrNum() {
num++;
}
/**
* Creates a new AtomicInteger with initial value {@code 0}.
*/
volatile AtomicInteger atomicInteger2Num = new AtomicInteger();
public void atomicIncrNum() {
atomicInteger2Num.getAndIncrement();
}
}
/**
* VolatileKeywordTest
* <br/>
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020/12/29
* @copyright 蛋壳创意科技 - www.dankal.cn
*/
class InstructionRearrangementTest {
public static void main(String[] args) {
// 指令 1
int a = 1;
// 指令 2
int b = 3;
// 指令 3
a += 5;
// 指令 4
b = a + 3;
// 顺序执行
// 1 2 3 4
// 指令重排
// 2 1 3 4
// 1 3 2 4
// 1 2 4 3
}
}
# 蛋壳创意科技-技术分享-Volatile关键字
# 蛋壳创意科技-技术分享-Volatile关键字
> Company : [蛋壳创意科技](https://www.dankal.cn/)
>
> Code: [dankal-share-volatile]()
## 梗概
> Volatile 关键字是 Java 虚拟机提供的最轻量级的同步机制
>
> 其有以下三种特性
>
> > - 保证可见性
> > - 禁止指令重排
> > - 不保证原子性
## 首先我们需要了解什么是`JMM`
### 什么是JMM
JMM : Java Memory Model (Java 内存模型)
> JMM本身是一种抽象的概念 并不真实存在,它描述的是一组规则或规范通过规范定制了程序中各个变量
>
> (包括实例字段,静态字段和构成数组对象的元素)的访问方式.
### JMM对同步的规定
> 1. 同类线程使用的必须同一把锁
> 2. 线程加锁前,必须把主内存中变量的最新值加载到自己的工作内存中
> 3. 线程释放锁前,必须把工作内存中共享变量的新值刷回到主内存中
### JMM的细节小说明
> ​ 由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方成为栈空间),工作内存是每个线程的<span style="color:red;">私有数据区域</span>,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可访问,但线程对变量的操作(读取赋值等)必须在工作内存中进行,首先要将变量从主内存拷贝到自己的工作空间,然后对变量进行操作,操作完成再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存储存着主内存中的变量副本拷贝,因此不同的线程无法访问对方的工作内存,如此线程间的通讯(传值) 必须通过主内存来完成,其简要访问过程如下图:
>
> ![](https://img.mercymodest.com/public/20200915205505.png)
## `Volatile` 保证 数据可见性示例
### 未使用 `Volatile` 关键字
#### 示例代码
```java
package cn.dankal.share;
import java.util.concurrent.TimeUnit;
/**
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020-12-29
* @Copyright 蛋壳创意科技 - www.dankal.cn
*/
public class VolatileVisibilityTest {
public static void main(String[] args) {
DataOperator dataOperator = new DataOperator();
new Thread(() -> {
try {
// 休眠 3 秒
TimeUnit.SECONDS.sleep(3);
dataOperator.setNum2New(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " num :" + dataOperator.num);
}, "edit num thread").start();
while (dataOperator.num == 0) {
// 阻塞 main 线程
}
System.out.println(Thread.currentThread().getName() + " the num is " + dataOperator.num + " not zero");
}
}
class DataOperator {
int num = 0;
public void setNum2New(int newNum) {
this.num = newNum;
}
}
```
#### 结果截图
![](https://img.mercymodest.com/public/20200915211130.png)
### 使用`Volatile `关键字
#### 测试代码
```java
package cn.dankal.share;
import java.util.concurrent.TimeUnit;
/**
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020-12-29
* @Copyright 蛋壳创意科技 - www.dankal.cn
*/
public class VolatileVisibilityTest {
public static void main(String[] args) {
DataOperator dataOperator = new DataOperator();
new Thread(() -> {
try {
// 休眠 3 秒
TimeUnit.SECONDS.sleep(3);
dataOperator.setNum2New(30);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " num :" + dataOperator.num);
}, "edit num thread").start();
while (dataOperator.num == 0) {
// 阻塞 main 线程
}
System.out.println(Thread.currentThread().getName() + " the num is " + dataOperator.num + " not zero");
}
}
class DataOperator {
volatile int num = 0;
public void setNum2New(int newNum) {
this.num = newNum;
}
}
```
#### 测试结果截图
![](https://img.mercymodest.com/public/20200915211246.png)
## `volatile`无法保证原子性示例
#### 代码示例
```java
package cn.dankal.share;
import java.util.Random;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020-12-29
* @Copyright 蛋壳创意科技 - www.dankal.cn
*/
public class VolatileVisibilityTest {
public static void main(String[] args) throws InterruptedException {
final DataOperator dataOperator = new DataOperator();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
20,
35,
10,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1 << 10),
runnable -> {
Thread thread = new Thread(runnable);
thread.setName("测试 Volatile 不保证原子性线程 -- " + new Random().nextInt(100000));
return thread;
}
);
// 计数扣减 保证 main 线程 在 20 个子线程之后执行
final CountDownLatch countDownLatch = new CountDownLatch(20);
final int threadCount = 20;
for (int i = 0; i < threadCount; i++) {
threadPoolExecutor.execute(() -> {
final int executeCount = 1000;
for (int j = 0; j < executeCount; j++) {
dataOperator.incrNum();
}
countDownLatch.countDown();
});
}
// 等待 20 个线程执行完成
countDownLatch.await();
System.out.println("the executor result: " + dataOperator.num);
threadPoolExecutor.shutdown();
}
}
class DataOperator {
volatile int num = 0;
public void setNum2New(int newNum) {
this.num = newNum;
}
public void incrNum() {
num++;
}
}
```
#### 运行结果截图
![](https://img.mercymodest.com/public/20200915212816.png)711
#### 就上述自增性问题如何在使用Volatile的情况下保证原子性示例
```java
package cn.dankal.share;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020-12-29
* @Copyright 蛋壳创意科技 - www.dankal.cn
*/
public class VolatileKeywordTest {
public static void main(String[] args) throws InterruptedException {
final DataOperator dataOperator = new DataOperator();
ThreadPoolExecutor threadPoolExecutor = getThreadPoolExecutor("test volatile with atomic -- ");
// 计数扣减 保证 main 线程 在 20 个子线程之后执行
final CountDownLatch countDownLatch = new CountDownLatch(20);
final int threadCount = 20;
for (int i = 0; i < threadCount; i++) {
threadPoolExecutor.execute(() -> {
System.out.println(Thread.currentThread().getName() + " do incr by atomic");
final int executeCount = 1000;
for (int j = 0; j < executeCount; j++) {
dataOperator.atomicIncrNum();
}
countDownLatch.countDown();
});
}
// 等待 20 个线程执行完成
countDownLatch.await();
System.out.println("the executor result: " + dataOperator.atomicInteger2Num.get());
threadPoolExecutor.shutdown();
}
/**
* 获取 线程池
*
* @param threadNamePrefix 线程池线程名称前缀
* @return ThreadPoolExecutor
*/
private static ThreadPoolExecutor getThreadPoolExecutor(String threadNamePrefix) {
return new ThreadPoolExecutor(
20,
35,
10,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1 << 10),
runnable -> {
Thread thread = new Thread(runnable);
thread.setName(threadNamePrefix + thread.getId());
return thread;
}
);
}
}
class DataOperator {
volatile int num = 0;
public void setNum2New(int newNum) {
this.num = newNum;
}
public void incrNum() {
num++;
}
/**
* Creates a new AtomicInteger with initial value {@code 0}.
*/
volatile AtomicInteger atomicInteger2Num = new AtomicInteger();
public void atomicIncrNum() {
atomicInteger2Num.getAndIncrement();
}
}
```
#### 运行结果截图
![](https://img.mercymodest.com/public/20200916090634.png)
## `volatile`保证有序性示例
### 首先先了解什么是指令重排
> 计算机在执行程序时,为了提高性能,编译器和处理器常常会做指令重排,一把分为以下3种
>
> ![](https://img.mercymodest.com/public/20200916092606.png)
>
> - 单线程环境里面确保程序最终执行结果和代码顺序执行的结果一致.
> - 处理器在进行重新排序是必须要考虑指令之间的数据依赖性
> - 多线程环境中线程交替执行,由于编译器优化重排的存在,两个线程使用的变量能否保持一致性是无法确定的,结果无法预测
#### 代码示例
```java
/**
*
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020-12-29
* @Copyright 蛋壳创意科技 - www.dankal.cn
*/
class InstructionRearrangementTest {
public static void main(String[] args) {
// 指令 1
int a = 1;
// 指令 2
int b = 3;
// 指令 3
a += 5;
// 指令 4
b = a + 3;
// 顺序执行
// 1 2 3 4
// 指令重排
// 2 1 3 4
// 1 3 2 4
// 1 2 4 3
}
}
```
![](https://img.mercymodest.com/public/20200916093837.png)
### 指令重排小析
> ![](https://img.mercymodest.com/public/20200916094305.png)
>
> ![](https://img.mercymodest.com/public/20200916094318.png)
## 先来看一段代码
```java
package cn.dankal.share;
/**
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020-12-29
* @Copyright 蛋壳创意科技 - www.dankal.cn
*/
public class DclSingletonTest {
public static void main(String[] args) {
SingletonInstance singletonInstance = SingletonInstance.getInstance();
}
}
class SingletonInstance {
private static SingletonInstance singletonInstance;
private SingletonInstance() {
}
public static SingletonInstance getInstance() {
if (null == singletonInstance) {
synchronized (SingletonInstance.class) {
if (null == singletonInstance) {
singletonInstance = new SingletonInstance();
}
}
}
return singletonInstance;
}
}
```
上述方式实现的 DCL单实例 真的是 线程安全的吗?
让我们先来看如下分析
> DCL(双端检锁) 机制不一定线程安全,原因是有指令重排的存在,加入volatile可以禁止指令重排
>
> 原因在于 线程A 在执行到第一次检测,读取到的singletonInstance不为null时, singletonInstance的引用对象可能没有完成初始化.
>
> singletonInstance=new SingletonInstance(); 可以分为以下步骤(伪代码)
>
> //步骤1.分配对象内存空间
>
> memory=allocate();
>
> //步骤2.初始化对象
>
> singletonInstance(memory);
>
> //步骤 3.设置instance的指向刚分配的内存地址,此时singletonInstance!=null
>
> singletonInstance=memory;
>
> 步骤2和步骤3不存在数据依赖关系.而且无论重排前还是重排后程序执行的结果在单线程中并没有改变,因此这种重排优化是允许的.
>
> //步骤1.分配对象内存空间
>
> memory=allocate();
>
> //步骤 3.设置instance的指向刚分配的内存地址,此时singletonInstance!=null
>
> singletonInstance=memory;
>
> 但对象还没有初始化完.
>
> //步骤2.初始化对象
>
> instance(memory);
>
>
>
> 但是指令重排只会保证串行语义的执行一致性(单线程) 并不会关心多线程间的语义一致性
> 所以当一条线程访问singletonInstance不为null时,由于singletonInstance实例未必完成初始化,也就造成了线程安全问题.
#### 使用 `volatile`改进 DCL 单例模式
```java
package cn.dankal.share;
/**
* @author ZGH.MercyModest
* @version V1.0.0
* @create 2020-12-29
* @Copyright 蛋壳创意科技 - www.dankal.cn
*/
public class DclSingletonTest {
public static void main(String[] args) {
SingletonInstance singletonInstance = SingletonInstance.getInstance();
}
}
class SingletonInstance {
private static volatile SingletonInstance singletonInstance;
private SingletonInstance() {
}
public static SingletonInstance getInstance() {
if (null == singletonInstance) {
synchronized (SingletonInstance.class) {
if (null == singletonInstance) {
singletonInstance = new SingletonInstance();
}
}
}
return singletonInstance;
}
}
```
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment