Java编程思想——多线程的三大核心源码层解密

简介:  欢迎大家关注,欢迎评论对于Java并发编程,一般来说有以下的关注点:1.线程安全性,正确性。2.线程的活跃性(死锁,活锁)3.性能其中线程的安全性问题是首要解决的问题,线程不安全,运行出来的结果和预期不一致,那就连基本要求都没达到了。

 

欢迎大家关注,欢迎评论

对于Java并发编程,一般来说有以下的关注点:

1.线程安全性,正确性。

2.线程的活跃性(死锁,活锁)

3.性能

其中线程的安全性问题是首要解决的问题,线程不安全,运行出来的结果和预期不一致,那就连基本要求都没达到了。

保证线程的安全性问题,本质上就是保证线程同步,实际上就是线程之间的通信问题。我们知道,在操作系统中线程通信有以下几种方式:

1.信号量 2.信号 3.管道 4.共享内存 5.消息队列 6.socket

java中线程通信主要使用共享内存的方式。共享内存的通信方式首先要关注的就是可见性有序性。而原子性操作一般都是必要的,所以主要关注这三个问题。

1、原子性(Atomicity)

原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,既不被中断操作,要不执行完成,要不就不执行。

JMM只是保证了基本的原子性,但类似于i++之类的操作,看似是原子操作,其实里面涉及到:

获取 i 的值。

自增。

再赋值给 i。

这三步操作,所以想要实现i++这样的原子操作就需要用到synchronize或者是lock进行加锁处理。

如果是基础类的自增操作可以使用AtomicInteger这样的原子类来实现(其本质是利用了CPU级别的 的CAS指令来完成的)。

其中用的最多的方法就是:incrementAndGet()以原子的方式自增。 源码如下:

首先是获得当前的值,然后自增 +1。接着则是最核心的compareAndSet()来进行原子更新。

其逻辑就是判断当前的值是否被更新过,是否等于current,如果等于就说明没有更新过然后将当前的值更新为next,如果不等于则返回false进入循环,直到更新成功为止。

还有其中的get()方法也很关键,返回的是当前的值,当前值用了volatile关键词修饰,保证了内存可见性。

private volatile int value;

2、可见性

可见性就是指当一个线程修改了线程共享变量的值,其它线程能够立即得知这个修改。

现代计算机中,由于CPU直接从主内存中读取数据的效率不高,所以都会对应的CPU高速缓存,先将主内存中的数据读取到缓存中,线程修改数据之后首先更新到缓存,之后才会更新到主内存。如果此时还没有将数据更新到主内存其他的线程此时来读取就是修改之前的数据。

如上图所示。

volatile关键字就是用于保证内存可见性,当线程A更新了 volatile 修饰的变量时,它会立即刷新到主线程,并且将其余缓存中该变量的值清空,导致其余线程只能去主内存读取最新值。

使用volatile关键词修饰的变量每次读取都会得到最新的数据,不管哪个线程对这个变量的修改都会立即刷新到主内存。

synchronize和加锁也能能保证可见性,实现原理就是在释放锁之前其余线程是访问不到这个共享变量的。但是和volatile相比开销较大。

3、顺序性

Java内存模型中的程序天然有序性可以总结为一句话:如果在本线程内观察,所有操作都是有序的;如果在一个线程中观察另一个线程,所有操作都是无序的。前半句是指“线程内表现为串行语义”,后半句是指“指令重排序”现象和“工作内存中主内存同步延迟”现象。

正常情况下的执行顺序应该是1>>2>>3。但是有时JVM为了提高整体的效率会进行指令重排导致执行的顺序可能是2>>1>>3。但是JVM也不能是什么都进行重排,是在保证最终结果和代码顺序执行结果一致的情况下才可能进行重排。

重排在单线程中不会出现问题,但在多线程中会出现数据不一致的问题。

Java 中可以使用volatile来保证顺序性, 和 lock也可以来保证有序性,和保证原子性的方式一样,通过同一段时间只能一个线程访问来实现的。

除了通过volatile关键字显式的保证顺序之外,JVM还通过happen-before原则来隐式的保证顺序性。

其中有一条就是适用于volatile关键字的,针对于volatile关键字的写操作肯定是在读操作之前,也就是说读取的值肯定是最新的。

volatile 的应用

双重检查锁的单例模式

可以用volatile实现一个双重检查锁的单例模式:

这里的volatile关键字主要是为了防止指令重排。 如果不用volatile,singleton = new Singleton();,这段代码其实是分为三步:

分配内存空间。(1)

初始化对象。(2)

将singleton对象指向分配的内存地址。(3)

加上volatile是为了让以上的三步操作顺序执行,反之有可能第二步在第三步之前被执行就有可能某个线程拿到的单例对象是还没有初始化的,以致于报错。

控制停止线程的标记

这里如果没有用 volatile 来修饰 flag ,就有可能其中一个线程调用了stop()方法修改了 flag 的值并不会立即刷新到主内存中,导致这个循环并不会立即停止。

这里主要利用的是volatile的内存可见性。

总结一下:

volatile关键字只能保证可见性,顺序性,不能保证原子性。

 

欢迎工作一到五年的Java工程师朋友们加入Java架构开发:468947140

点击链接加入群聊【Java-BATJ企业级资深架构】:https://jq.qq.com/?_wv=1027&k=5zMN6JB

本群提供免费的学习指导 架构资料 以及免费的解答

不懂得问题都可以在本群提出来 之后还会有职业生涯规划以及面试指导

如果觉得本文还可以,欢迎大家《关注》,算是对笔者的支持

相关文章
|
10天前
|
监控 Java 调度
【Java学习】多线程&JUC万字超详解
本文详细介绍了多线程的概念和三种实现方式,还有一些常见的成员方法,CPU的调动方式,多线程的生命周期,还有线程安全问题,锁和死锁的概念,以及等待唤醒机制,阻塞队列,多线程的六种状态,线程池等
68 6
【Java学习】多线程&JUC万字超详解
|
3天前
|
Java 调度 开发者
Java并发编程:深入理解线程池
在Java的世界中,线程池是提升应用性能、实现高效并发处理的关键工具。本文将深入浅出地介绍线程池的核心概念、工作原理以及如何在实际应用中有效利用线程池来优化资源管理和任务调度。通过本文的学习,读者能够掌握线程池的基本使用技巧,并理解其背后的设计哲学。
|
3天前
|
缓存 监控 Java
Java中的并发编程:理解并应用线程池
在Java的并发编程中,线程池是提高应用程序性能的关键工具。本文将深入探讨如何有效利用线程池来管理资源、提升效率和简化代码结构。我们将从基础概念出发,逐步介绍线程池的配置、使用场景以及最佳实践,帮助开发者更好地掌握并发编程的核心技巧。
|
4天前
|
缓存 监控 Java
java中线程池的使用
java中线程池的使用
|
4天前
|
算法 Java 数据处理
Java并发编程:解锁多线程的力量
在Java的世界里,掌握并发编程是提升应用性能和响应能力的关键。本文将深入浅出地探讨如何利用Java的多线程特性来优化程序执行效率,从基础的线程创建到高级的并发工具类使用,带领读者一步步解锁Java并发编程的奥秘。你将学习到如何避免常见的并发陷阱,并实际应用这些知识来解决现实世界的问题。让我们一起开启高效编码的旅程吧!
|
9天前
|
存储 Java 程序员
优化Java多线程应用:是创建Thread对象直接调用start()方法?还是用个变量调用?
这篇文章探讨了Java中两种创建和启动线程的方法,并分析了它们的区别。作者建议直接调用 `Thread` 对象的 `start()` 方法,而非保持强引用,以避免内存泄漏、简化线程生命周期管理,并减少不必要的线程控制。文章详细解释了这种方法在使用 `ThreadLocal` 时的优势,并提供了代码示例。作者洛小豆,文章来源于稀土掘金。
|
6天前
|
Java 开发者
Java中的多线程编程基础与实战
【9月更文挑战第6天】本文将通过深入浅出的方式,带领读者了解并掌握Java中的多线程编程。我们将从基础概念出发,逐步深入到代码实践,最后探讨多线程在实际应用中的优势和注意事项。无论你是初学者还是有一定经验的开发者,这篇文章都能让你对Java多线程有更全面的认识。
14 1
|
3天前
|
安全 Java UED
Java并发编程:解锁多线程的潜力
在Java的世界里,并发编程如同一场精心编排的交响乐,每个线程扮演着不同的乐手,共同奏响性能与效率的和声。本文将引导你走进Java并发编程的大门,探索如何在多核处理器上优雅地舞动多线程,从而提升应用的性能和响应性。我们将从基础概念出发,逐步深入到高级技巧,让你的代码在并行处理的海洋中乘风破浪。
|
4月前
|
存储 安全 Java
深入理解Java并发编程:线程安全与锁机制
【5月更文挑战第31天】在Java并发编程中,线程安全和锁机制是两个核心概念。本文将深入探讨这两个概念,包括它们的定义、实现方式以及在实际开发中的应用。通过对线程安全和锁机制的深入理解,可以帮助我们更好地解决并发编程中的问题,提高程序的性能和稳定性。
|
1月前
|
存储 安全 Java
解锁Java并发编程奥秘:深入剖析Synchronized关键字的同步机制与实现原理,让多线程安全如磐石般稳固!
【8月更文挑战第4天】Java并发编程中,Synchronized关键字是确保多线程环境下数据一致性与线程安全的基础机制。它可通过修饰实例方法、静态方法或代码块来控制对共享资源的独占访问。Synchronized基于Java对象头中的监视器锁实现,通过MonitorEnter/MonitorExit指令管理锁的获取与释放。示例展示了如何使用Synchronized修饰方法以实现线程间的同步,避免数据竞争。掌握其原理对编写高效安全的多线程程序极为关键。
48 1