java并发编程基础,java并发三大特性
本文介绍了java的一些知识,主要介绍了并发编程三要素的相关问题,包括原子性、可见性、有序性,以及它们出现的原因和定义等。下面我们一起来看看,希望对你有所帮助。
如何解决写爬虫IP受阻的问题?立即使用。
1 原子性
1.1 原子性的定义
原子性是指一个或多个操作,要么全部执行而不被其他操作中断,要么全部不执行。
1.2 原子性问题原因
线程切换是原子问题的原因,线程切换是为了提高CPU利用率。
以count为例,至少需要三条CPU指令:
1:首先,变量count需要从内存加载到CPU的寄存器中;2:之后,在寄存器中执行1次操作;3:最后将结果写入内存(缓存机制导致有可能写CPU缓存而不是内存)。让我们假设count=0。如果线程A在执行完指令1后切换线程,线程A和线程B按照如下所示的顺序执行,那么我们会发现两个线程都执行了count=1,但是得到的结果不是预期的2,而是1。
1.3 原子性操作
在多线程环境下,Java只保证基本数据类型的变量和赋值操作是原子的(注:在32位的JDK环境下,对64位数据的读取不是原子性操作*,如long、double)
1.4 原子性问题如何解决
如果我们能保证对共享变量的修改是互斥的,那么,单核CPU和多核CPU都可以保证原子性。锁定可以解决原子问题,比如使用synchronized和lock。
2 可见性
2.1 可见性定义
可见性是指当多个线程操作一个共享变量时,一个线程修改变量后,其他线程可以立即看到修改后的结果。
2.2 可见性问题原因
CPU缓存和内存的数据一致性是可见性问题的原因,CPU缓存是用来提高CPU效率的。
2.3 可见性问题解决
可见性问题是由CPU缓存引起的,因此我们可以禁用CPU缓存。
volatile字段可以禁用CPU缓存并解决可见性问题。synchronized和lock都可以保证可见性。
2.4 可见性规则是什么
可见性规则是发生在规则之前。
发生在规则之前:
简单来说:前面一个操作的结果对后续操作是可见的。发生之前约束编译器的优化行为。虽然允许编译器进行优化,但要求编译器在优化后必须遵守发生在之前的规则。
2.5 Happens-Before 规则
程序的顺序性规则在一个线程中,根据程序顺序,前一个操作发生——在任何后续操作之前。
课程示例{
公共无效测试(){
int x=42
int y=20
}
}发生-在之前.
volatile 变量规则写入一个volatile变量,发生在后续读取volatile变量之前。
传递性规则如果A发生-在B之前,而B发生-在C之前,那么A发生-在C之前。
课程示例{
int x=0;
volatile int y=0;
公共void编写器(){
x=42
y=1;
}
公共void读取器(){
if (y==1) {
//这里X会是多少?
}
}
}发生-在之前,满足规则1-顺序规则。发生-在之前,满足规则2-易变变量规则。发生-在之前,满足规则3-传递规则。如果y==1,那么x=42;管程中锁的规则开锁发生在锁的后续锁定之前。
Pipe是一个通用的同步原语,在Java中的意思是synchronized,synchronized是pipe在Java中的实现。
Synchronized (this) {//此处自动锁定
//x是共享变量,初始值=10。
如果(this.x 12) {
this.x=12
}
}//这里自动解锁假设X的初始值为10,线程A执行完代码块后X的值会变成12(执行后自动解除锁);
当线程B进入代码块时,可以看到线程A对X的写操作,即线程B可以看到x==12。
线程 start() 规则表示主线程A提升线程B后,子线程B可以先于提升线程B看到主线程的操作。
线程 join() 规则表示主线程A等待子线程B结束(主线程A通过调用子线程B的join()方法实现这一点)。当子线程B结束时(主线程A中的join()方法返回),主线程可以看到子线程的操作。当然,所谓“看见”指的是共享变量的操作。
3 有序性
3.1 有序性的定义
有序性,即程序的执行顺序是按照代码的顺序执行的。
3.2 有序性问题原因
编译器为了优化性能,有时会改变程序中语句的顺序。
比如:“A=6;b=7;”优化后,编译器可能会变成“b=7;a=6;”在这个例子中,编译器调整了语句的顺序,但这并不影响程序的最终结果。
以双重校验码为例:
公共类Singleton {
静态单一实例;
静态Singleton getInstance(){
if (instance==null) {
synchronized(Singleton.class) {
if (instance==null)
instance=new Singleton();
}
}
返回实例;
}
}上面的代码有问题。问题出在操作上:优化后的执行路径如下:
分配一块内存m;将m的地址赋给实例变量;最后在内存m上初始化Singleton对象,优化后会导致什么问题?我们假设线程A先执行getInstance()方法,当它执行完时,恰好线程切换到线程B;如果此时线程B也执行getInstance()方法,那么线程B在执行第一次判断时会找到Instance!=null,所以直接返回实例,此时的实例是未初始化的。如果我们此时访问实例的成员变量,可能会触发空指针异常。
如何解决双检的问题?变量用 volatile 来修饰,禁止指令重排序。
公共类Singleton {
静态可变单例实例;
静态Singleton getInstance(){
if (instance==null) {
synchronized(Singleton.class) {
if (instance==null)
instance=new Singleton();
}
}
返回实例;
}
}推荐学习:《java视频教程》以上是为了深入了解Java并发编程三要素的细节。更多请关注我们的其他相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。