二、多线程存在的问题和Java内存模型

 2019-12-10 16:13  阅读(1124)
文章分类:Java Core

多线程存在的问题

多线程运用得好可以大大提高系统的性能。但是使用不当也会对系统造成毁灭性灾难。

  1. 线程安全问题。多个线程操作共享数据时,会产生线程安全问题。导致读取脏数据或者丢失更新等问题
  2. 线程活性问题。由于程序问题导致一个线程一直处于非Runnable状态或者处于Runnable状态但执行的任务没有紧张称为线程活性问题。例如:两个线程,线程1需要先占用锁1,再占用锁2。线程2需要先占用锁2,再占用锁1。这是如果线程1占用了锁1,线程2占用了锁2。他们都占用了对方需要的锁,双方都阻塞等待对方的锁释放,导致死锁。
  3. 上下文切换。线程切换引起的上下文切换,会增加系统消耗。

线程安全问题

线程安全问题是多个线程在操作共享数据引起的。要保证线程安全,就需要保证对共享数据的操作有三个性质:原子性,可见性和有序性。

原子性

原子性是指涉及共享数据的操作对别的线程是不可分割的。即其他线程只能看到该操作未发生或者已经结束。

注意 i++ 并不是原子性操作,i++实际上是一个`read-modify-write`操作。
    1. 先读取出i的值
    2. 修改i的值
    3. 写回内存

可见性

可见性是指一个线程对共享数据修改后,其他线程可以看到修改后的值。

1. 由于java内存模型中,每个线程都有一个工作内存。在对共享数据进行修改和读取时,
    是先对工作内存中的数据进行操作。所以其他线程读取的共享变量可能是脏数据,无法保证可见性。
    2. 重排序导致的有序性问题也是影响可见性的重要因素。

Java内存模型 java内存模型,简称JMM。java线程之间的通信是通过JMM控制的。JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有其工作内存,存有共享变量的副本。线程对共享变量的读写都是先对工作内存进行,工作内存在将共享变量和内存同步。

工作内存是抽象概念,并非真实存在。它蕴含了缓存,写缓存区,寄存器还有其他硬件等。

20191210001660\_1.png

有序性

JIT编译器为了优化系统,会对代码进行重排序。重排序按照as-if-serial语义,保证重排序后在单线程时运行结果是一样的。但是多线程时,无法保证有序性。

代码经过各级重排序优化再最终执行 20191210001660\_2.png

happens-before规则是JMM对多线程重排序的约束规则,遵循happens-before规则的重排序不会改变多线程的执行结果。

int a=1; //A
    int b=3; //B
    int c=a+b; //C

A happens-before B(非必须)

A happens-before C

B happens-before C

JMM对happens-before的定义:

  1. 如果一个操作happens-before另一个操作,那么操作一的执行结果对第二个操作时可见的,并且第一个操作执行顺序在第二个操作之前。
  2. 两个操作如果存在happens-before规则,并不意味者java平台会按照happens-before的执行顺序执行。如果重排序的执行结果和按happens-before顺序执行的结果一致的话,jmm允许这种排序。

规则1是JMM对程序员的保证,而规则2是JMM对编译器和处理器重排序的约束

下面是happens-before原则规则:

  1. 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;
  2. 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作;
  3. volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
  4. 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
  5. 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;
  6. 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
  7. 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
  8. 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;

happens-before规则也保证了可见性,先执行的操作结果对后面执行的操作是可见的。

happens-before推导

private volatile boolean flag;
        private int i;
        public void read(){
            i=1;   //1
            flag=true;  //2
        }
        public  void write(){
            if(flag){   // 3
                int j=i;  //4  
            }
        }
  1. 根据程序规则,1 happens-before 2 ; 3 happens-before 4
  2. 根据volatile规则,2 happens-before 3
  3. 根据传递性规则,由 1 happens-before 2,2 happens-before 3,3 happens-before 4 => 1 happens before 4

20191210001660\_3.png

线程安全解决方案

多线程安全问题是因为多个线程同时操作共享变量,缺乏同步机制来协调线程间数据的访问和活动。jdk提供了锁,volatile关键字等线程同步机制

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

相关推荐