线程和Java内存模型

 2019-12-10 11:25  阅读(701)
文章分类:Java Core

硬件的效率和一致性

CPU的性能 = IPC * MHz

IPC主要是指的利用进程间的通信来提升CPU的性能(Amdahl定律,通过系统中的串行和并行的比重来提升系统西能),而MHz则是指信号交换频率(摩尔定律,处理器的晶体管数量越多,运行效率就越高)

![生产联盟 > 线程和Java内存模型 > image2017-12-14 14:15:38.png” src=”http://ddrvcn.oss-cn-hangzhou.aliyuncs.com/2019/3/2UnUF3.png”>

Java的内存模型

简介

java 也是分为主内存和工作内存。java中所有的变量都存在于主内存中,此处变量主要是实例字段,静态的字段,构成数组对象的字段,不包括局部变量和方法参数,因为后两者都是线程私有的,不会被共享,所以不会出现竞争问题。每个线程都有自己的工作内存,每个线程的工作内存中保存了主内存中的变量的副本拷贝,每次线程执行到更新操作的时候,需要先更新副本变量,再同步到主内存中,也就是说,读写操作都在工作内存中进行,不能直接读写主内存中的变量,即使volatile也不例外。不同线程之间的变量访问也是通过主内存进行的。需要注意的是,这个主内存和工作内存的划分和堆、栈、方法区的内存区域划分是两个不同层次的划分,两者基本没有关系。从变量、主内存和工作内存的定义看来,主内存更像是堆,工作内存像是栈。但实际上堆里面还有其他的信息,比如GC标志,年龄,同步锁,存储对象的HashCode等。从更低层的角度看的话,主内存对应于物理硬件的内存,虚拟机首先保证执行的应该是工作内存,甚至工作内存的优先存储级别应该在寄存器和高速缓存之上,因为程序运行时主要访问读写的操作就是在工作内存中进行的。

内存间的交互

也就是主内存和工作内存之间具体的交互方式。一个变量如何从主内存读取到工作内存,又如何更新到主内存中的实现?

这个实际上是java内存模型中定义了8种操作:{lock,unlock},{read,load,use},{assign,store,write}。这8种操作需要保证是原子性操作。凡事容易有例外,long,double的load,store,read,write在32位的操作系统上可能就会被划分为2次32位的操作,所以,java虚拟机也允许未被volatile修饰的变量的上述四种操作不保持原子性。假设多个线程同时对一个没有volatile修饰的变量进行赋值和读取,就可能会出现读取的值既不是原值也不是新的值,而是一个啥也不是的东西。

java内存模型规定了在执行上述8种基本操作的时候的一些规则:

1、不允许read和load,store和write的操作之一单独出现,也就是不能出现读取了主内存的值,但是不给工作内存的副本变量或者写入到主内存,但是主内存不接受的情况。

2、不允许线程丢弃最近的assign操作,一旦在工作内存中将变量改变了,就需要同步回主内存中。

3、如果一个线程中的变量没有被assign,不允许写入到主内存中。

4、新的变量只能在主内存中出现,然后才能到工作内存,也就是一个变量在use和store之前肯定是要先执行assign和load操作。

5、一个变量同一时间只能有一个线程对其进行lock操作,如果重复执行多次,需要执行同样多次的unlock才能解锁成功。

6、一个变量执行了lock操作,就会在工作内存中将其清空值,这样再使用的时候就需要再重新load或assign操作初始化变量的值。

7、一个变量事先没有被lock锁定,就不能被unlock。

8、对一个变量执行unlock操作之前,必须先把变量同步回主内存。

上面的8种情况基本上已经确定了java那些内存访问操作在并发下是安全的。不过还需要volatile变量的一些描述。

volatile关键字

什么是线程不安全的?也就是一个线程A在操作时间上先发生于线程B,但是无法确认B执行后的值。比如setter,getter

volatile是java提供的最轻量级的同步机制。被volatile修饰的变量有两种特性:①保证变量对所有的线程都是立即可见的。②禁止指令重排序。

volatile修饰的变量在不同的线程工作内存中也是可以不一致的,这一点和上面的第一种特性并不冲突,因为读取和赋值是多个指令操作,我们看一段字节码,就可以看出问题。

| packagemain;/******@authorlihaitao*@since设计wiki|需求wiki*/publicclassVolatileTest{publicstaticvolatileintrace=0;publicstaticvoidincrease(){race++;/*Java字节码的指令publicstaticvoidincrease();Code:0:getstatic#2//Fieldrace:I3:iconst_14:iadd5:putstatic#2//Fieldrace:I8:return*/}publicstaticfinalintTHREAD_COUNT=20;publicstaticvoidmain(String[]args){Thread[]threads=newThread[THREAD_COUNT];for(Threadthread:threads){thread=newThread(newRunnable(){@Overridepublicvoidrun(){for(inti=0;i<10000;i++){increase();}}});thread.start();}while(Thread.activeCount()>1){Thread.yield();}System.out.println(race);}}publicmain.VolatileTest();Code:0:aload_01:invokespecial#1//Methodjava/lang/Object.][__Java__image2017-12-1414_15_38.png_src_http_ddrvcn.oss-cn-hangzhou.aliyuncs.com_2019_3_2UnUF3.png_p_h2_Java_h2_h3_strong_strong_h3_p_java_java_volatile_GC_HashCode_p_h3_strong_strong_h3_p_p_p_java_8_lock_unlock_read_load_use_assign_store_write_8_long_double_load_store_read_write_32_2_32_java_volatile_volatile_p_p_java_8_p_p_1_read_load_store_write_p_p_2_assign_p_p_3_assign_p_p_4_use_store_assign_load_p_p_5_lock_unlock_p_p_6_lock_load_assign_p_p_7_lock_unlock_p_p_8_unlock_p_p_8_java_volatile_p_h3_volatile_h3_p_A_B_B_setter_getter_p_p_volatile_java_volatile_p_p_volatile_p_table_tbody_tr_td_pre_packagemain____volatile______authorlihaitao__since_wiki__wiki_publicclassVolatileTest_publicstaticvolatileintrace_0_publicstaticvoidincrease__race__Java_publicstaticvoidincrease_Code_0_getstatic_2_Fieldrace_I3_iconst_14_iadd5_putstatic_2_Fieldrace_I8_return__publicstaticfinalintTHREAD_COUNT_20_publicstaticvoidmain_String_args__Thread_threads_newThread_THREAD_COUNT_for_Threadthread_threads__thread_newThread_newRunnable___Overridepublicvoidrun__for_inti_0_i_10000_i__increase____thread.start__while_Thread.activeCount__1__Thread.yield__System.out.println_race___publicmain.VolatileTest_Code_0_aload_01_invokespecial_1_Methodjava_lang_Object.]上面的increase方法中race++成了4条字节码指令,如果在第二条和第三条执行的时候,其他的线程进行了操作,这样该线程就可能会把小的值写回主内存。当然,字节码的执行指令只有一条也不意味着是原子性操作,因为到解释器进行解释执行的时候可能需要执行多行代码实现。但现在已经能反映问题了,``````我也试着进行反汇编,但是oracle的官网对一些命令包已经不支持,所以也不了了之。第二条特性是禁止重新排序,也就是禁止代码执行顺序和代码编写顺序不一致。虚拟机或者物理机为了性能都有可能进行乱序执行的操作,比如说下面的代码|packagemain;importjava.util.LinkedList;importjava.util.List;/***&lt;一句话描述&gt;*&lt;java代码只是伪代码,因为排序问题是在反汇编之后执行的时候可能会出现问题,这里只是用java代码进行演示&gt;**@authorlihaitao*@since设计wiki|需求wiki*/publicclassFieldResolution{Stringa;Stringb;List&lt;String&gt;list;booleanaBoolean=false;Stringc="c";{/***假设下面的赋值由线程A操作*是一些初始化的操作...*/a="a";b="b";list=newLinkedList&lt;&gt;();aBoolean=true;Stringc="c";}{/***该代码块由线程B操作*/if(aBoolean){//dosomething}}}||:-----:||packagemain;importjava.util.LinkedList;importjava.util.List;/***&lt;一句话描述&gt;*&lt;java代码只是伪代码,因为排序问题是在反汇编之后执行的时候可能会出现问题,这里只是用java代码进行演示&gt;**@authorlihaitao*@since设计wiki|需求wiki*/publicclassFieldResolution{Stringa;Stringb;List&lt;String&gt;list;booleanaBoolean=false;Stringc="c";{/***假设下面的赋值由线程A操作*是一些初始化的操作...*/a="a";b="b";list=newLinkedList&lt;&gt;();aBoolean=true;Stringc="c";}{/***该代码块由线程B操作*/if(aBoolean){//dosomething}}}|volatile是怎么实现的其他线程能获取到最新的值呢?在volatile修饰的变量进行assign的操作的时候,会进行一个lock指令,该指令会禁止后面的指令进行重排序,也会让该缓存写入到内存中,顺便让其他的线程中该变量的缓存失效,这样,其它线程再去读取该变量的时候需要重新的进行load,use操作。``````volatile有好处,也有不好的地方,不能完全保证数据的一致性,那volatile的应用场景是什么?答案是不需要依赖数据的原值的情况,也就是我对这个变量的写操作不需要依赖于该变量的原值,我只需要赋值,其他线程再进行读取的时候就是一个正确的值。###Java内存模型的三个特性:可见性、原子性、有序性。###可见性就是一旦一个变量被修改了,其他线程能立即知道这个变量的最新值。原子性是指的操作一次性完成,不会存在一部分完成的情况,虽然long,double是有非原子性操作协定,但是一般的虚拟机实现都会将这两个实现成原子性操作。所以我们不需要太关心。有序性就是一个线程本身执行是有序的,但是别的线程观察该线程的时候,就会发现其执行可能是乱序的。##Java与线程##并发并不一定是线程粒度,可能是进程(PHP),java中的并发一般都是和线程挂钩的。###实现线程的3种方式和Java中的线程实现###1、利用内核线程关于内核线程就是由操作系统内核直接支持的线程。这种线程切换由内核进行处理,操作线程调度器进行调度线程,将线程的任务映射到各个处理器上。用户的程序一般不是直接使用内核线程,而是使用实现LWP(lightweightprocess)轻量级进程方式,轻量级进程是内核线程的一种高级接口。一般的情况是用户的程序一个进程分成多个线程,每个线程都继承LWP,每个LWP对应一个支持的KLT(kernel-levelThread),先支持内核线程,才有轻量级进程。轻量级线程和内核线程是1:1的关系。2、利用用户线程也就是不用内核线程进行调度维护,完完全全是用户程序去操作线程的切换调度,这样不需要内核额外的开销。看起来不错,但是用户的程序如果线程切换、创建、调度都是很麻烦的甚至不能处理,因为处理器只会向进程进行资源分配。所以java,ruby等都放弃了这个方式。3、内核线程和用户线程共用这种方式比较好,既有内核线程进行调度,又因为有很多用户线程执行任务,消耗内核资源较少。用户线程和LWP的比例为N:M。但是没有成为主流,不知道是实现比较难还是有其他的问题。java中实现的方式是第一种###Java线程的调度###两种调度方式:协同式调度,抢占式调度。java采用的是抢占式调度。###Java线程的状态###新建状态。运行状态。无限等待状态。wait()、join()、LockSupport.park()有限等待状态。sleep(),wait(),join()还有LockSupport的几个method阻塞状态。阻塞状态只出现在竞争的时候死亡状态。挂了。参考资料:周老师的《深入java虚拟机》[__Java__image2017-12-1414_15_38.png_src_http_ddrvcn.oss-cn-hangzhou.aliyuncs.com_2019_3_2UnUF3.png_p_h2_Java_h2_h3_strong_strong_h3_p_java_java_volatile_GC_HashCode_p_h3_strong_strong_h3_p_p_p_java_8_lock_unlock_read_load_use_assign_store_write_8_long_double_load_store_read_write_32_2_32_java_volatile_volatile_p_p_java_8_p_p_1_read_load_store_write_p_p_2_assign_p_p_3_assign_p_p_4_use_store_assign_load_p_p_5_lock_unlock_p_p_6_lock_load_assign_p_p_7_lock_unlock_p_p_8_unlock_p_p_8_java_volatile_p_h3_volatile_h3_p_A_B_B_setter_getter_p_p_volatile_java_volatile_p_p_volatile_p_table_tbody_tr_td_pre_packagemain____volatile______authorlihaitao__since_wiki__wiki_publicclassVolatileTest_publicstaticvolatileintrace_0_publicstaticvoidincrease__race__Java_publicstaticvoidincrease_Code_0_getstatic_2_Fieldrace_I3_iconst_14_iadd5_putstatic_2_Fieldrace_I8_return__publicstaticfinalintTHREAD_COUNT_20_publicstaticvoidmain_String_args__Thread_threads_newThread_THREAD_COUNT_for_Threadthread_threads__thread_newThread_newRunnable___Overridepublicvoidrun__for_inti_0_i_10000_i__increase____thread.start__while_Thread.activeCount__1__Thread.yield__System.out.println_race___publicmain.VolatileTest_Code_0_aload_01_invokespecial_1_Methodjava_lang_Object.]:| | :-----: | | packagemain;/******@authorlihaitao*@since设计wiki|需求wiki*/publicclassVolatileTest{publicstaticvolatileintrace=0;publicstaticvoidincrease(){race++;/*Java字节码的指令publicstaticvoidincrease();Code:0:getstatic#2//Fieldrace:I3:iconst_14:iadd5:putstatic#2//Fieldrace:I8:return*/}publicstaticfinalintTHREAD_COUNT=20;publicstaticvoidmain(String[]args){Thread[]threads=newThread[THREAD_COUNT];for(Threadthread:threads){thread=newThread(newRunnable(){@Overridepublicvoidrun(){for(inti=0;i<10000;i++){increase();}}});thread.start();}while(Thread.activeCount()>1){Thread.yield();}System.out.println(race);}}publicmain.VolatileTest();Code:0:aload_01:invokespecial#1//Methodjava/lang/Object.][__Java__image2017-12-1414_15_38.png_src_http_ddrvcn.oss-cn-hangzhou.aliyuncs.com_2019_3_2UnUF3.png_p_h2_Java_h2_h3_strong_strong_h3_p_java_java_volatile_GC_HashCode_p_h3_strong_strong_h3_p_p_p_java_8_lock_unlock_read_load_use_assign_store_write_8_long_double_load_store_read_write_32_2_32_java_volatile_volatile_p_p_java_8_p_p_1_read_load_store_write_p_p_2_assign_p_p_3_assign_p_p_4_use_store_assign_load_p_p_5_lock_unlock_p_p_6_lock_load_assign_p_p_7_lock_unlock_p_p_8_unlock_p_p_8_java_volatile_p_h3_volatile_h3_p_A_B_B_setter_getter_p_p_volatile_java_volatile_p_p_volatile_p_table_tbody_tr_td_pre_packagemain____volatile______authorlihaitao__since_wiki__wiki_publicclassVolatileTest_publicstaticvolatileintrace_0_publicstaticvoidincrease__race__Java_publicstaticvoidincrease_Code_0_getstatic_2_Fieldrace_I3_iconst_14_iadd5_putstatic_2_Fieldrace_I8_return__publicstaticfinalintTHREAD_COUNT_20_publicstaticvoidmain_String_args__Thread_threads_newThread_THREAD_COUNT_for_Threadthread_threads__thread_newThread_newRunnable___Overridepublicvoidrun__for_inti_0_i_10000_i__increase____thread.start__while_Thread.activeCount__1__Thread.yield__System.out.println_race___publicmain.VolatileTest_Code_0_aload_01_invokespecial_1_Methodjava_lang_Object.]上面的increase方法中race++成了4条字节码指令,如果在第二条和第三条执行的时候,其他的线程进行了操作,这样该线程就可能会把小的值写回主内存。当然,字节码的执行指令只有一条也不意味着是原子性操作,因为到解释器进行解释执行的时候可能需要执行多行代码实现。但现在已经能反映问题了,``````我也试着进行反汇编,但是oracle的官网对一些命令包已经不支持,所以也不了了之。第二条特性是禁止重新排序,也就是禁止代码执行顺序和代码编写顺序不一致。虚拟机或者物理机为了性能都有可能进行乱序执行的操作,比如说下面的代码|packagemain;importjava.util.LinkedList;importjava.util.List;/***&lt;一句话描述&gt;*&lt;java代码只是伪代码,因为排序问题是在反汇编之后执行的时候可能会出现问题,这里只是用java代码进行演示&gt;**@authorlihaitao*@since设计wiki|需求wiki*/publicclassFieldResolution{Stringa;Stringb;List&lt;String&gt;list;booleanaBoolean=false;Stringc="c";{/***假设下面的赋值由线程A操作*是一些初始化的操作...*/a="a";b="b";list=newLinkedList&lt;&gt;();aBoolean=true;Stringc="c";}{/***该代码块由线程B操作*/if(aBoolean){//dosomething}}}||:-----:||packagemain;importjava.util.LinkedList;importjava.util.List;/***&lt;一句话描述&gt;*&lt;java代码只是伪代码,因为排序问题是在反汇编之后执行的时候可能会出现问题,这里只是用java代码进行演示&gt;**@authorlihaitao*@since设计wiki|需求wiki*/publicclassFieldResolution{Stringa;Stringb;List&lt;String&gt;list;booleanaBoolean=false;Stringc="c";{/***假设下面的赋值由线程A操作*是一些初始化的操作...*/a="a";b="b";list=newLinkedList&lt;&gt;();aBoolean=true;Stringc="c";}{/***该代码块由线程B操作*/if(aBoolean){//dosomething}}}|volatile是怎么实现的其他线程能获取到最新的值呢?在volatile修饰的变量进行assign的操作的时候,会进行一个lock指令,该指令会禁止后面的指令进行重排序,也会让该缓存写入到内存中,顺便让其他的线程中该变量的缓存失效,这样,其它线程再去读取该变量的时候需要重新的进行load,use操作。``````volatile有好处,也有不好的地方,不能完全保证数据的一致性,那volatile的应用场景是什么?答案是不需要依赖数据的原值的情况,也就是我对这个变量的写操作不需要依赖于该变量的原值,我只需要赋值,其他线程再进行读取的时候就是一个正确的值。###Java内存模型的三个特性:可见性、原子性、有序性。###可见性就是一旦一个变量被修改了,其他线程能立即知道这个变量的最新值。原子性是指的操作一次性完成,不会存在一部分完成的情况,虽然long,double是有非原子性操作协定,但是一般的虚拟机实现都会将这两个实现成原子性操作。所以我们不需要太关心。有序性就是一个线程本身执行是有序的,但是别的线程观察该线程的时候,就会发现其执行可能是乱序的。##Java与线程##并发并不一定是线程粒度,可能是进程(PHP),java中的并发一般都是和线程挂钩的。###实现线程的3种方式和Java中的线程实现###1、利用内核线程关于内核线程就是由操作系统内核直接支持的线程。这种线程切换由内核进行处理,操作线程调度器进行调度线程,将线程的任务映射到各个处理器上。用户的程序一般不是直接使用内核线程,而是使用实现LWP(lightweightprocess)轻量级进程方式,轻量级进程是内核线程的一种高级接口。一般的情况是用户的程序一个进程分成多个线程,每个线程都继承LWP,每个LWP对应一个支持的KLT(kernel-levelThread),先支持内核线程,才有轻量级进程。轻量级线程和内核线程是1:1的关系。2、利用用户线程也就是不用内核线程进行调度维护,完完全全是用户程序去操作线程的切换调度,这样不需要内核额外的开销。看起来不错,但是用户的程序如果线程切换、创建、调度都是很麻烦的甚至不能处理,因为处理器只会向进程进行资源分配。所以java,ruby等都放弃了这个方式。3、内核线程和用户线程共用这种方式比较好,既有内核线程进行调度,又因为有很多用户线程执行任务,消耗内核资源较少。用户线程和LWP的比例为N:M。但是没有成为主流,不知道是实现比较难还是有其他的问题。java中实现的方式是第一种###Java线程的调度###两种调度方式:协同式调度,抢占式调度。java采用的是抢占式调度。###Java线程的状态###新建状态。运行状态。无限等待状态。wait()、join()、LockSupport.park()有限等待状态。sleep(),wait(),join()还有LockSupport的几个method阻塞状态。阻塞状态只出现在竞争的时候死亡状态。挂了。参考资料:周老师的《深入java虚拟机》[__Java__image2017-12-1414_15_38.png_src_http_ddrvcn.oss-cn-hangzhou.aliyuncs.com_2019_3_2UnUF3.png_p_h2_Java_h2_h3_strong_strong_h3_p_java_java_volatile_GC_HashCode_p_h3_strong_strong_h3_p_p_p_java_8_lock_unlock_read_load_use_assign_store_write_8_long_double_load_store_read_write_32_2_32_java_volatile_volatile_p_p_java_8_p_p_1_read_load_store_write_p_p_2_assign_p_p_3_assign_p_p_4_use_store_assign_load_p_p_5_lock_unlock_p_p_6_lock_load_assign_p_p_7_lock_unlock_p_p_8_unlock_p_p_8_java_volatile_p_h3_volatile_h3_p_A_B_B_setter_getter_p_p_volatile_java_volatile_p_p_volatile_p_table_tbody_tr_td_pre_packagemain____volatile______authorlihaitao__since_wiki__wiki_publicclassVolatileTest_publicstaticvolatileintrace_0_publicstaticvoidincrease__race__Java_publicstaticvoidincrease_Code_0_getstatic_2_Fieldrace_I3_iconst_14_iadd5_putstatic_2_Fieldrace_I8_return__publicstaticfinalintTHREAD_COUNT_20_publicstaticvoidmain_String_args__Thread_threads_newThread_THREAD_COUNT_for_Threadthread_threads__thread_newThread_newRunnable___Overridepublicvoidrun__for_inti_0_i_10000_i__increase____thread.start__while_Thread.activeCount__1__Thread.yield__System.out.println_race___publicmain.VolatileTest_Code_0_aload_01_invokespecial_1_Methodjava_lang_Object.]: |

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> 线程和Java内存模型

相关推荐