深入理解JUC:第一章:volatile的三大特性

import java.util.concurrent.atomic.AtomicInteger;

class MyData{

volatile int number = 0;

public synchronized void changeData(){

number++;//加一

}

}

/**

  • 线程对变量的读取赋值要先将变量从主内存拷贝自己的工作内存空间,在工作内存中进行操作,操作完成后再将变量写回主内存

*/

public class Demo {

//主线程main,程序入口

public static void main(String[] args) {

//创建对象,number在主内存为0

MyData myData = new MyData();

for (int i = 1; i <= 20; i++) {

//创建20个线程

new Thread(()->{

//一个线程执行1000次加一的操作

for (int j = 1; j <= 1000; j++) {

myData.changeData();

}

},String.valueOf(i)).start();

}

//程序不关闭会继续执行main线程和GC线程,判断线程数量大于二继续执行上面的代码,

while (Thread.activeCount() > 2){

Thread.yield();

}

//理想中number的数量为20*1000=20000,而volatile不保证原子性,实际情况一般打印number的数量不是20000

System.out.println(Thread.currentThread().getName()+“\t 打印number的数量:” + myData.number);

}

}

使用AtomicInteger原子整型

package com.javaliao.backstage;

import java.util.concurrent.atomic.AtomicInteger;

class MyData{

volatile int number = 0;

AtomicInteger atomicInteger = new AtomicInteger();

public void changeData(){

atomicInteger.getAndIncrement();//加一

}

}

/**

  • 线程对变量的读取赋值要先将变量从主内存拷贝自己的工作内存空间,在工作内存中进行操作,操作完成后再将变量写回主内存

*/

public class Demo {

//主线程main,程序入口

public static void main(String[] args) {

//创建对象,number在主内存为0

MyData myData = new MyData();

for (int i = 1; i <= 20; i++) {

//创建20个线程

new Thread(()->{

//一个线程执行1000次加一的操作

for (int j = 1; j <= 1000; j++) {

myData.changeData();

}

},String.valueOf(i)).start();

}

//程序不关闭会继续执行main线程和GC线程,判断线程数量大于二继续执行上面的代码,

while (Thread.activeCount() > 2){

Thread.yield();

}

//理想中number的数量为20*1000=20000,而volatile不保证原子性,实际情况一般打印number的数量不是20000

System.out.println(Thread.currentThread().getName()+“\t 打印number的数量:” + myData.atomicInteger);

}

}

什么是JMM的有序性?

数据依赖性怎么理解?就是先有你爸再有你。

什么是指令重排?

假设我写的第20行代码,执行的时候不一定会从第一行执行到第20行,打个比方:参加高考做卷子,出题人给的题目,你不一定会从第一题做到最后一题,你可能会先把会的写了,其他有难度的题目最后写。

代码案例1:

再多线程环境下语句执行有1234和2134以及1324三种顺序,语句4不能重排后变成第一个,原因是什么说的数据依赖性,变量要先声明再使用。

案例2:

可以看到变量的值有二套,很恐怖的好吧,所以volatile需要禁止指令重排,确认最终变量的值。

案例3:

在多线程环境下指令重排,会导致二种结果:一个是0+5=5,一个是1+5=6

正常单线程环境下会执行语句1再执行语句2最后执行语句3,结果打印为6

多线程环境下指令重排了先执行语句2再执行语句3最后执行语句1,结果打印为5

很恐怖的好吧,数据的一致性不能保证,所以volatile需要禁止指令重排。

volatile禁止指令重排小总结

单例模式在多线程环境下可能存在安全问题

单线程下的单例模式:

package com.javaliao.backstage;

class MyData{

private static MyData myData = null;

private MyData(){

System.out.println(Thread.currentThread().getName()+“\t 构造方法”);

}

public static MyData getInstance(){

if(myData == null){

myData = new MyData();

}

return myData;

}

}

public class Demo {

//主线程main,程序入口

public static void main(String[] args) {

System.out.println(MyData.getInstance() == MyData.getInstance());

System.out.println(MyData.getInstance() == MyData.getInstance());

}

}

控制台打印正确:

多线程下再使用这样的单例模式:

package com.javaliao.backstage;

public class Demo {

private static Demo demo = null;

private Demo(){

System.out.println(Thread.currentThread().getName()+“\t 构造方法”);

}

public static Demo getInstance(){

if(demo == null){

demo = new Demo();

}

return demo;

}

//主线程main,程序入口

public static void main(String[] args) {

for (int i = 1; i <= 10; i++) {

new Thread(()->{

Demo.getInstance();

},String.valueOf(i)).start();

}

}

}

控制台打印错误:

本来应该打印一次的,结果10个线程打印了5次,这就有问题了

有人会说加一个synchronized

public class Demo {

private static Demo demo = null;

private Demo(){

System.out.println(Thread.currentThread().getName()+“\t 构造方法”);

}

public static synchronized Demo getInstance(){

if(demo == null){

demo = new Demo();

}

return demo;

}

//主线程main,程序入口

public static void main(String[] args) {

for (int i = 1; i <= 10; i++) {

new Thread(()->{

Demo.getInstance();

},String.valueOf(i)).start();

}

}

}

控制台打印正确:

但是这个不好,synchronized是重量型的,数据一致型得到保证了,但是影响并发

那怎么解决这个问题呢?

这个时候先介绍DCL,后面再一点点讲解。

什么是DCL双关检锁机制?

加锁前后都进行一次判断。

直接上代码理解,DCL(双关检锁机制)代码:

package com.javaliao.backstage;

public class Demo {

private static Demo demo = null;

private Demo(){

System.out.println(Thread.currentThread().getName()+“\t 构造方法”);

}

public static Demo getInstance(){

if(demo == null){

synchronized (Demo.class){

if(demo == null){

demo = new Demo();

}

}

}

return demo;

}

//主线程main,程序入口

public static void main(String[] args) {

for (int i = 1; i <= 10; i++) {

new Thread(()->{

Demo.getInstance();

},String.valueOf(i)).start();

}

}

}

为什么要加二层判断呢?更加牢固一些

打个比方:你上厕所,你确认没有人了,然后再进去,进去了把门插上,再推一下门,推不动确认安全后该干啥干啥。

在多线程环境下使用DCL(双关检锁机制)是否就百分百OK呢?

不是,DCL(双关检锁机制)不一定线程安全,在多线程环境下,JMM中的有序性会让指令出现重排,让执行顺序发送变化,不能保证百分百。加入volatile可以禁止指令重排

单例模式volatile分析

在多线程环境下,当一条线程访问instance不为null时,由于instance实例未必已初始化完成,造成线程安全问题。

所以加上volatile才可以保证百分百ok

public class Demo {

private static volatile Demo demo = null;

private Demo(){

System.out.println(Thread.currentThread().getName()+“\t 构造方法”);

}

public static Demo getInstance(){

if(demo == null){

synchronized (Demo.class){

if(demo == null){

demo = new Demo();

}

}

}

return demo;

}

最后

作为过来人,小编是整理了很多进阶架构视频资料、面试文档以及PDF的学习资料,针对上面一套系统大纲小编也有对应的相关进阶架构视频资料


adow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2phdmFfd3hpZA==,size_16,color_FFFFFF,t_70)

在多线程环境下,当一条线程访问instance不为null时,由于instance实例未必已初始化完成,造成线程安全问题。

所以加上volatile才可以保证百分百ok

public class Demo {

private static volatile Demo demo = null;

private Demo(){

System.out.println(Thread.currentThread().getName()+“\t 构造方法”);

}

public static Demo getInstance(){

if(demo == null){

synchronized (Demo.class){

if(demo == null){

demo = new Demo();

}

}

}

return demo;

}

最后

作为过来人,小编是整理了很多进阶架构视频资料、面试文档以及PDF的学习资料,针对上面一套系统大纲小编也有对应的相关进阶架构视频资料

[外链图片转存中…(img-OJyXLpDB-1720118075784)]
[外链图片转存中…(img-5hQxFJcI-1720118075785)]

  • 10
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值