方法一:为变量添加修饰:volatile(易变化关键字)
volatile static boolean isrun = true ;
这样做的目的是:加上volatile 的变量,每次循环都是只能在主存当中获取,不会从高速缓存区中获取!
测试结果:T1线程停止
方法二:使用synchronized
在Java内存模型中,synchronized规定,线程在加锁时, 先清空工作内存→在主内存中拷贝最新变量的副本到工作内存 →执行完代码→将更改后的共享变量的值刷新到主内存中→释放互斥锁。(线程获得对象锁后,会清空工作区内存,重新在主存中获取!)
可见性VS原子性
重点区分:volatile和synchronized ;
-
我们的volatile只能保证线程看到的变量是实时的,但是并不能保证是安全的!
-
多个线程同时访问,即使被volatile修饰,仍然可能会出现指令交错问题!
有序性
JVM会在不影响正确的条件下,调整语句的执行顺序!这种特性称作【指令重排】
//如下i和j的++操作调换顺序不影响结果!
public class ReSortTest {
static int i = 0 ;
static int j = 0 ;
public static void main(String[] args) {
i++; //修改为j++
j++; //修改为i++
}
}
思考:正常执行是正确的,而且多线程条件下指令重排可能是会出现问题的,为什么要进行指令重排的优化呢?
指令重排序优化
事实上,现代处理器会设计为一个时钟周期完成一条执行时间最长的CPU指令。为什么这么做呢?可以想到指令还可以再划分成一个个更小的阶段,例如,每条指令都可以分为: 取指令—指令译码—执行指令—内存访问—数据写回这5个阶段
重排之前:指令串行执行!
现代CPU支持多级指令流水线,例如支持同时执行取指令~指令译码–执行指令–内存访问–数据写回的处理器,就可以称之为五级指令流水线。这时CPU可以在一个时钟周期内,同时运行五条指令的不同阶段(相当于一条执行时间最长的复杂指令),IPC =1,本质上,流水线技术并不能缩短单条指令的执行时间,但它变相地提高了指令地吞吐率。
重拍之后:指令并行执行 !
总结:指令级别的优化,我们线程的不同指令的不同阶段可同时进行!【指令级别的并发】
重排序的目的:为的是一个指令执行某一个阶段的时候,通过重排序,让其他执行执行其他的阶段!达到最大的指令并发!
当然前提是:重排互不影响结果 !
public class ReSortTest {
static int i = 0 ;
static int j = 0 ;
public static void main(String[] args) {
i++; //2条指令可重排序!
j++;
i= j - 10 ; //不可重排序,会影响结果
j++ ;
}
}
禁止指令重排序
可以使用volatile实现,因为volatile可以使得被修饰的变量之前的操作是不会被重排序的
Volatile原理 *
以上可以了解到Volatile可以保证共享变量的有序性、可见性 , 我们接下来了解一下原理 ;
volatile的底层实现原理是内存屏障,Memory Barrier (Memory Fence)
-
对volatile变量的写指令后会加入写屏障 ;
-
对volatile变量的读指令前会加入读屏障 ;
1、如何保证的可见性
- 写屏障(sfence)保证在该屏障之前的,对共享变量的改动,都同步到主存当中
public class ReSortTest {
static int i = 0 ;
volatile static int j = 0 ;
public static void main(String[] args) {
i++;
j++; //由于j是volatile修饰的,此处对j进行写操作,所以插入写屏障 !
//所以j++ 以及之前的代码全部会被同步到主存当中
}
}
- 读屏障(lfence)保证的是在该屏障之前的,对共享变量的改动,都同步到主存当中!
public class ReSortTest {
volatile static int j = 0 ;
public static void main(String[] args) {
if(j > 1){ //读取j ,在此之前的代码插入读屏障!(保证读取到的是主存中最新的数据!)
}
}
}
2、如何保证有序性
- 写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
public class ReSortTest {
static int i = 0 ;
volatile static int j = 0 ;
public static void main(String[] args) {
i++;
j++; //由于j是volatile修饰的,此处对j进行写操作,所以插入写屏障 !
//所以j++ 以及之前的代码全部会被同步到主存当中
//写屏障 , 之前的代码不会发生指令重排序!
}
}
- 读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
public class ReSortTest {
volatile static int j = 0 ;
public static void main(String[] args) {
//读屏障:之后的代码不会被指令重排序
if(j > 1){ //读取j ,在此之前的代码插入读屏障!(保证读取到的是主存中最新的数据!)
}
}
}
总结:
-
读屏障之后的代码不会发产生指令重排序、而且读到的都是主存中的数据
-
写屏障之前的代码不会发生指令重排序、而且之前的代码会全部更新在主存当中!
虽然能解决可见性和有序性,但是仍然不能解决指令交错问题(原子性) ;
3、DCL(Double Check Locking)问题的分析、纠正、解决
happens before规则
七大规则(保证共享变量可见性的七种方法)!
CAS + Volatile 无锁实现并发,保证线程安全(乐观锁)
CAS的工作方式
CAS (Compare And Set) : 比较并设置
//测试代码!
static AtomicInteger baclace ;
public void withdraw(Integer amount){
while(true){
int pre = balance.get() ;
int next = pre - amount ;
if (balance.ComapreAndSet(pre,next)) break ; //比较并设置设置值
}
}
//多个线程访问如下方法
其中ComapreAndSet,简称就是CAS(也有Compare And Swap的说法) ,它必须是原子操作!
当CAS方法执行时,prev 会与主存的实时balance比较一次,如果发现不一致(其他线程修改了),那么就返回false ;
//源码
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update); //执行cas时expect会与自身value比较
}
CAS 与 volatile
获取共享变量时,为了保证该变量的可见性,需要使用volatile修饰。
它可以用来修饰成员变量和静态成员变量,他可以避免线程从自己的工作缓存中查找变量的值,必须到主存中获取它的值,线程操作volatile变量都是直接操作主存。即一个线程对volatile变量的修改,对另一个线程可见。
注意
volatile仅仅保证了共享变量的可见性,让其它线程能够看到最新值,但不能解决指令交错问题(不能保证原子性)
//在我们原子整数当中,value都是被volatile修饰过的!
private volatile int value;
CAS必须借助volatile才能读取到共享变量的最新值来实现【比较并交换】的效果
为什么无锁效率高?
无锁情况下,即使重试失败,线程始终在高速运行,没有停歇,而synchronized 会让线程在没有获得锁的时候,发生上下文切换,进入阻塞。
而且最好用于线程数少于核心数的情况,线程数多的话CAS所在线程分不到时间片依然会进行上下文切换!
总结:因为CAS无锁保证线程安全的话,线程不会说会受到其他线程的影响陷入BLOCK阻塞状态,而是多个线程都会操作共享对象,但是cas会一直比较保证线程安全,线程是不会停止的,sync有锁方式则会出现一个线程获得锁,其他线程只能陷入BLOCK状态等待!
CAS的特点
-
结合CAS和volatile可以实现无锁并发,适用于线程数少、多核CPU的场景下。
-
CAS是基于乐观锁的思想:最乐观的估计,不怕别的线程来修改共享变量,就算改了也没关系,我点再重试呗。
-
synchronized是基于悲观锁的思想:最悲观的估计,得防着其它线程来修改共享变量,我上了锁你们想改,我改完了解开锁,你们才有机会。
-
CAS体现的是无锁并发、无阻塞并发,请仔细体会这两句话的意思
-
因为没有使用synchronized,所以线程不会陷入阻塞,这是效率提升的因素之一·
-
但如果竞争激烈,可以想到重试必然频繁发生,反而效率会受影响
原子整数
JUC的子包 java.util.concurrent.atomic 提供了
-
AtomicInteger
-
AtomicBoolean
-
AtomicLong
AtomicInteger为例:
AtomicInteger i = new AtomicInteger(0);
//下边方法属于原子方法,线程安全的!
System.out.println(i.getAndIncrement()); // 结果为 0 等价 i ++ (线程不安全的!)
System.out.println(i.incrementAndGet()); // 结果为 2 等价 ++ i
System.out.println(i.getAndAdd(5));// 结果2
System.out.println(i.addAndGet(5));// 结果12
读取到的 要更改为
System.out.println(i.updateAndGet(x -> x * 10)); //输出 50
//本质都是compare and set ;
原子引用
除了保护我们的基本类型,还可以保护BigDecimal这种引用类型 ;
//测试代码!
private AtomicReference baclace ; //外加一层AtomicReference
public void withdraw(Integer amount){
while(true){
BigDecimal pre = balance.get() ;
BigDecimal next = pre.subtract(amount) ; //引用数据类型减法
if (balance.ComapreAndSet(pre,next)) break ; //比较并设置设置值
}
}
BigDecimal decimal = new BigDecimal(“1000”); //初始化时最好传递的时字符串!
ABA问题
我们都知道我们cas保证的时最新的值和pre是否相等来判断是否被修改,但是存在这么一种情况:值被修改但是,修改后还是跟pre一致,这种情况,cas则无法判断是否被修改过 ;(虽然对业务无影响,但是仍是个隐患!)
AtomicStampedReference
因此,为了解决ABA这种问题引入
AtomicStampedReference str = new AtomicStampedReference<>(“a”,0); // 0相当于版本号,只要修改过就会 + 1
//除了比较值是否相等还会比较版本号,版本号会记录改过的次数
AtomicMarkableReference
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

那么如何才能正确的掌握Redis呢?
为了让大家能够在Redis上能够加深,所以这次给大家准备了一些Redis的学习资料,还有一些大厂的面试题,包括以下这些面试题
-
并发编程面试题汇总
-
JVM面试题汇总
-
Netty常被问到的那些面试题汇总
-
Tomcat面试题整理汇总
-
Mysql面试题汇总
-
Spring源码深度解析
-
Mybatis常见面试题汇总
-
Nginx那些面试题汇总
-
Zookeeper面试题汇总
-
RabbitMQ常见面试题汇总
JVM常频面试:
Mysql面试题汇总(一)
Mysql面试题汇总(二)
Redis常见面试题汇总(300+题)
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门即可获取!
tyle=“zoom: 33%;” />
那么如何才能正确的掌握Redis呢?
为了让大家能够在Redis上能够加深,所以这次给大家准备了一些Redis的学习资料,还有一些大厂的面试题,包括以下这些面试题
-
并发编程面试题汇总
-
JVM面试题汇总
-
Netty常被问到的那些面试题汇总
-
Tomcat面试题整理汇总
-
Mysql面试题汇总
-
Spring源码深度解析
-
Mybatis常见面试题汇总
-
Nginx那些面试题汇总
-
Zookeeper面试题汇总
-
RabbitMQ常见面试题汇总
JVM常频面试:
[外链图片转存中…(img-ahvpVT97-1712037963588)]
Mysql面试题汇总(一)
[外链图片转存中…(img-XNRj16D3-1712037963589)]
Mysql面试题汇总(二)
[外链图片转存中…(img-JNUYOMRp-1712037963589)]
Redis常见面试题汇总(300+题)
[外链图片转存中…(img-rHpvHVAQ-1712037963589)]
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门即可获取!