Java多线程详解(原子性、有序性、可见性),夯实基础就在今天

《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!
为什么需要并发,多线程?

  1. 时代的召唤,为了更充分的利用多核CPU的计算能力,多个线程程序可通过提高处理器的资源利用率来提升程序性能。

  2. 方便业务拆分,异步处理业务,提高应用性能。

多线程并发产生的问题?

  1. 大量的线程让CPU频繁上下文切换带来的系统开销。

  2. 临界资源线程安全问题(共享,可变)。

  3. 容易造成死锁。

注意:当多个线程执行一个方法时,该方法内部的局部变量并不是临界资源,因为这些局部变量是在每个线程的私有栈中,因此不具有共享性质,不会导致线程安全问题。

可见性

多线程访问同一个变量时,如果有一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。这是因为为了保证多个CPU之间的高速缓存是一致的,操作系统会有一个缓存一致性协议,volatile就是通过OS的缓存一致性协议策略来保证了共享变量在多个线程之间的可见性。

public class ThreadDemo2 {

private static boolean flag = false;

public void thread_1(){

flag = true;

System.out.println(“线程1已对flag做出改变”);

}

public void thread_2(){

while (!flag){

}

System.out.println(“线程2->flag已被修改,成功打断循环”);

}

public static void main(String[] args) {

ThreadDemo2 threadDemo2 = new ThreadDemo2();

Thread thread2 = new Thread(()->{

threadDemo2.thread_2();

});

Thread thread1= new Thread(()->{

threadDemo2.thread_1();

});

thread2.start();

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

thread1.start();

}

}

执行结果

线程1已对flag做出改变

代码无论执行多少次,线程2的输出语句都不会被打印。为flag添加volatile修饰后执行,线程2执行的语句被打印

执行结果

线程1已对flag做出改变

线程2->flag已被修改,成功打断循环

局限:volatile只是保证共享变量的可见性,无法保证其原子性。多个线程并发时,执行共享变量i的i++操作<==> i = i + 1,这是分两步执行,并不是一个原子性操作。根据缓存一致性协议,多个线程读取i并对i进行改变时,其中一个线程抢先独占i进行修改,会通知其他CPU我已经对i进行修改,把你们高速缓存的值设为无效并重新读取,在并发情况下是可能出现数据丢失的情况的。

public class ThreadDemo3 {

private volatile static int count = 0;

public static void main(String[] args) {

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

Thread thread = new Thread(()->{

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

count++;

}

});

thread.start();

}

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(“count执行的结果为->” + count);

}

}

执行结果

count执行的结果为->9561

注意:这个结果是不固定的,有时10000,有时少于10000。

原子性

就像恋人一样同生共死,表现在多线程代码中程序一旦开始执行,就不会被其他线程干扰要嘛一起成功,要嘛一起失败,一个操作不可被中断。在上文的例子中,为什么执行结果不一定等于10000,就是因为在count++是多个操作,1.读取count值,2.对count进行加1操作,3.计算的结果再赋值给count。这几个操作无法构成原子操作的,在一个线程读取完count值时,另一个线程也读取他并给它赋值,根据缓存一致性协议通知其他线程把本次读取的值置为无效,所以本次循环操作是无效的,我们看到的值不一定等于10000,如何进行更正---->synchronized关键字

public class ThreadDemo3 {

private volatile static int count = 0;

private static Object object = new Object();

public static void main(String[] args) {

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

Thread thread = new Thread(()->{

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

synchronized (object){

count++;

}

}

});

thread.start();

}

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(“count执行的结果为->” + count);

}

}

执行结果

count执行的结果为->10000

加锁后,线程在争夺执行权就必须获取到锁,当前线程就不会被其他线程所干扰,保证了count++的原子性,至于synchronized为什么能保证原子性,篇幅有限,下一篇在介绍。

有序性

jmm内存模型允许编译器和CPU在单线程执行结果不变的情况下,会对代码进行指令重排(遵守规则的前提下)。但在多线程的情况下却会影响到并发执行的正确性。

public class ThreadDemo4 {

private static int x = 0,y = 0;

private static int a = 0,b = 0;

private static int i = 0;

public static void main(String[] args) throws InterruptedException {

for (;😉{

i++;

x = 0;y = 0;

a = 0;b = 0;

最后

码字不易,觉得有帮助的可以帮忙点个赞,让更多有需要的人看到

又是一年求职季,在这里,我为各位准备了一套Java程序员精选高频面试笔试真题,来帮助大家攻下BAT的offer,题目范围从初级的Java基础到高级的分布式架构等等一系列的面试题和答案,用于给大家作为参考

以下是部分内容截图
架构面试专题及架构学习笔记导图.png
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

最后

码字不易,觉得有帮助的可以帮忙点个赞,让更多有需要的人看到

又是一年求职季,在这里,我为各位准备了一套Java程序员精选高频面试笔试真题,来帮助大家攻下BAT的offer,题目范围从初级的Java基础到高级的分布式架构等等一系列的面试题和答案,用于给大家作为参考

以下是部分内容截图
[外链图片转存中…(img-uXzYKmvE-1714445741952)]
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门,即可获取!

  • 15
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值