java多线程有几种实现方法实战(Java基础——Java多线程(volalite详解))java基础 / Java多线程基础...

wufei123 发布于 2024-06-21 阅读(6)

1 基本概括

2 主要介绍2.1 Java内存模型(JMM)JMM(Java Memory Model)Java内存模型,是java虚拟机规范中所定义的一种内存模型Java内存模型(Java Memory Model)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取变量这样的底层细节。

所有的共享变量都存储于主内存这里所说的变量指的是实例变量和类变量不包含局部变量,因为局部变量是线程私有的,因此不存在竞争问题每一个线程还存在自己的工作内存,线程的工作内存,保留了被线程使用的变量的工作副本。

线程对变量的所有的操作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量,不同线程之间也不能直接访问对方工作内存中的变量,线程间变量的值的传递需要通过主内存完成 2.1.1 Java内存模型以及操作规范变量

1.共享变量必须存放在主存中;2.线程有自己的工作内存,线程只可操作自己的工作内存;3.线程要操作共享变量,需从主存中读取到工作内存,改变之后需从工作内存同步到主存中Java内存模型会带来什么问题?有变量A,多线程并发对其累加会有什么问题?如三个线程并发操作变量A,大家读取A时读到A=0,

都对A+1,在将值同步会主内存,结果是多少?结果是1.这就是线程安全问题如何解决线程安全问题?内存模型也产生了变量可见性问题如何让线程2使用A时看到最新值?1.线程1修改A后必须马上同步回主内存2.线程2使用A前需从主内存读取到工作内存。

疑问一:使用前不会重新从主内存读取到工作内存吗?疑问二:修改后不会立马同步会主内存吗? 2.1.2.Java内存模型–同步交互协议规定了8种原子操作:1.lock(锁定):将主内存中的变量锁定,为一个线程所独占

2.unlock(解锁):将lock加的锁定解除,此时其它的线程可以有机会访问此变量3.read(读取):作用于主存变量,将主存中的变量读到工作内存当中4.load(载入):作用于工作内存变量,将read读取的值保存到工作内存中的变量副本中

5.use(使用):作用于工作内存变量,将值传递给线程的代码执行引擎6.assign(赋值):作用于工作内存变量,将执行引擎处理返回的值重新赋值给变量副本7.store(存储):作用于工作内存变量,将变量副本的值传送到主内存中

8.write(写入):作用于主内存变量,将store传送过来的值写入到主内存的共享变量中操作规范:1.将一个变量从主内存复制到工作内存要顺序执行read load操作;要将变量从工作内同步回主内存要顺序执行store、write操作。

只要求顺序执行,不一定是连续执行2.做了assign操作,必须同步回主存。不能没做assign,同步回内存。

并发中保持变量可见性的方式:1.final变量//final不可变变量 private final int var1 = 1;2.Synchronizedwhile (VisibilityDemo.is

){ synchronized(this){ i++; } }3 用volatile修饰//状态标识 private static volatitle boolean is =true;2.2 多线程编程的三个概念(JMM定义)

2.2.1 原子性这一点,跟数据库事务的原子性概念差不多,即一个操作(有可能包含有多个子操作)要么全部执行(生效),要么全部都不执行(都不生效)2.2.2 可见性可见性是指,当多个线程并发访问共享变量时,一个线程对共享变量的修改,其它线程能够立即看到。

可见性问题是好多人忽略或者理解错误的一点2.2.3 有序性顺序性指的是,程序执行的顺序按照代码的先后顺序执行2.3 指令重排序2.3.1 什么是指令重排序执行任务的时候,为了提高编译器和处理器的执行性能,编译器和处理器(包括内存系统,内存在行为没有重排但是存储的时候是有变化的)会对指令重排序。

编译器优化的重排序是在编译时期完成的,指令重排序和内存重排序是处理器重排序编译器优化的重排序,在不改变单线程语义的情况下重新安排语句的执行顺序指指令级并行重排序,处理器的指令级并行技术将多条指令重叠执行,如果不存在数据的依赖性将会改变对应机器指令的执行顺序

内存系统的重排序,因为使用了读写缓存区,使得看起来并不是顺序执行的例子如下://section1inta = 1;intb = 1;a=a+1;b=b+1;//section2inta = 1;a=a+1;

intb = 1;b=b+1;第一部分的代码可能就不会有部分2的性能高,因为a可以直接从寄存器中取,不需要反复地拿a定义完a之后马上就可以进行add操作2.3.2 产生的问题重排序可能会导致多线程程序出现内存可见性问题。

(工作内存和主内存,编译器处理器重排序导致的可见性)重排序会导致有序性问题,程序的读写顺序与内存的读写顺序不一样(编译器处理器重排序,内存缓冲区(是处理器重排序的内容))2.4 happens-before

2.4.1定义1)如果一个操作happens-before另一个操作,那么第一个操作的执行结果将对第二个操作可见,而且第一个操作的执行顺序排在第二个操作之前2)两个操作之间存在happens-before关系,并不意味着Java平台的具体实现必须要按照happens-before关系指定的顺序来执行。

如果重排序之后的执行结果,与按happens-before关系来执行的结果一致,那么这种重排序并不非法(也就是说,JMM允许这种重排序)2.4.2 为什么需要happens-beforeJVM会对代码进行编译优化,会出现指令重排序情况

,为了避免编译优化对并发编程安全性的影响,需要happens-before规则定义一些禁止编译优化的场景,保证并发编程的正确性2.4.3 JMM提供的happens-before 保证原子性可见性和有序性。

8大准则:程序顺序原则,一个线程内要按照代码的顺序执行,保证语义的串行性锁规则,解锁操作必须发生在对同一个锁的加锁操作之前volatile规则,对volatile修饰的变量的写操作发生在volatile变量的读操作之前。

访问volatile的变量时候强制从主内存中进行读取,volatile修饰的变量被修改之后会被强制的刷新到主内存中所以说volatile修饰的变量保证了可见性线程启动规则,线程的start()方法最先执行,线程A在线程B执行start()方法之前对共享变量的修改对线程B可见

线程终止规则,线程的所有操作优于线程的终结,Thread.join()方法的作用是等待当前线程终止,线程B在终止之前修改了共享变量,执行完成后,线程B对共享变量的修改将对线程A可见线程中断规则,对线程interrupt中断方法的调用发生在被中断的线程检测到中断事件的发生之前

对象终结规则,对象的构造函数执行完成先于finalize()方法传递性,线程A生在线程B之前,线程B发生在线程C之前,则线程A发生在线程C之前2.4 volatile特征与原理2.4.1 volatile内存可见性

被 volatile 修饰的元数据在线程操纵它们的时候 JMM 有两个执行机制(load、store)可以保证元数据在内存中对所有线程的可见性load 机制:在每次有线程读取元数据的时候 JMM 都会执行load 操作把当前将要被操纵的元数据的最新值更新到内存中

store 机制:线程每次操作完元数据之后 JMM 都会执行 store 操作把当前操纵的元数据更新到内存中如下图所示:

2.4.2 volatile 防止重排序原理为了性能优化,JMM在不改变正确语义的前提下,会允许编译器和处理器对指令序列进行重排序,那如果想阻止重排序要怎么办了? 答案是可以添加内存屏障。

Java编译器会在生成指令系列时在适当的位置会插入内存屏障指令来禁止特定类型的处理器重排序 为了实现volatile的内存语义,JMM会限制特定类型的编译器和处理器重排序,JMM会针对编译器制定volatile重排序规则表:。

"NO"表示禁止重排序 为了实现volatile内存语义时,编译器在生成字节码时,会在指令序列中插入内存屏障来禁止特定类型的处理器重排序 对于编译器来说,发现一个最优布置来最小化插入屏障的总数几乎是不可能的,为此,JMM采取了保守策略:。

在每个volatile写操作的前面插入一个StoreStore屏障在每个volatile写操作的后面插入一个StoreLoad屏障

在每个volatile读操作的后面插入一个LoadLoad屏障在每个volatile读操作的后面插入一个LoadStore屏障

需要注意的是:volatile写操作是在前面和后面分别插入内存屏障,而volatile读操作是在后面插入两个内存屏障2.4 volatile为啥不能保证原子性修改volatile变量分为四步:1)读取volatile变量到local。

2)修改变量值3)local值写回4)插入内存屏障,即lock指令,让其他线程可见这样就很容易看出来,前三步都是不安全的,取值和写回之间,不能保证没有其他线程修改原子性需要锁来保证2.5 如何保证原子性。

主要三种方式 1 synchronized同步代码块 2 cas原子类工具 3 lock锁机制2.6 volatile的使用场景2.6.1 volatitle的使用范围:1.volatile只可修饰成员变量(静态的、非静态的)【全局变量】

2.多线程并发下,才需要使用它2.6.2 volatitle典型的应用场景:1、只有一个修改者,多个使用者,要求保证可见性的场景2、状态标识,如示例中介绍的标识2、数据定期发布,都给获取者3 常见用例3.1 volatile的应用双重检查锁的单例模式

publicclassSingleton{ privatestaticvolatile Singleton singleton; privateSingleton()

{ } publicstatic Singleton getInstance(){ if (singleton == null) {

1synchronized (Singleton.class) { if (singleton == null) { singleton =

new Singleton(); } } } return singleton; }

3.2 volatile不保证原子性例子/** * 看作jmm模型 */classTestVoliate {//jmm模型,主内存值为0volatileint number=0; //线程的工作内存进行了number++

publicvoidnumberIncrement(){ this.number++; } } classTestvolatileIncrementError {publicstatic

voidmain(String[] args){ TestVoliate myDate3 = new TestVoliate(); //开100个线程,每个线程做100次number++

for (int i = 1; i { for (int j = 0; j < 100

; j++) { myDate3.numberIncrement(); } }, String.valueOf(i)).start(); } System.out.println(Thread.currentThread().getName() +

"\t final number: "+myDate3.number); } } 运行结果: main final number: 95754 常见问题1讲讲什么是JMM2JMM定义了什么3八种内存交互操作

4volatile为啥不能保证原子性5volatile怎么禁止指令重排序6什么指令重排序7如何保证原子性 8long和double的原子性9单例的8种写法和优劣势10Volatile在双重检查加锁的单例中的应用

11volatile的使用场景12volatile与synchronized常见出现的问题会在后面的文章讨论,一起学习的朋友可以点点关注,会持续更新,文章有帮助的话可以长按点赞有惊喜!!!,收藏,转发,

有什么补充可以在下面评论,谢谢!

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

河南中青旅行社综合资讯 奇遇综合资讯 盛世蓟州综合资讯 综合资讯 游戏百科综合资讯 新闻78700