java并发编程基础,java并发三大特性

  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的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: