多线程并发常见问题

多线程并发常见问题

一 概述

1.volatile

保证共享数据一旦被修改就会当即同步到共享内存(堆或者方法区)中。html

2.线程访问堆中数据的过程

线程在栈中创建一个数据的副本,修改完毕后将数据同步到堆中。安全

3.指令重排

为了提升执行效率,CPU会将没有依赖关系的指令从新排序。若是但愿控制从新排序,可使用volatile修饰一个变量,包含该变量的指令先后的指令各自独立排序,先后指令不能交叉排序。多线程

二 常见问题及应对

1.原子性问题

所谓原子性,指的是一个操做不可中断,即在多线程并发的环境下,一个操做一旦开始,就会在同一个CPU时间片内执行完毕。若是同一个线程的多个操做在不一样的CPU时间片上执行,因为中间出现停滞,后面的操做在执行时可能某个共享数据被其余线程修改,而该修改并未同步到当前线程中,致使当前线程操做的数据与实际不符,这种因为执行不连贯致使的数据不一致问题被称做原子性问题。并发

2.可见性问题

可见性问题的出现与线程访问共享数据的方式有关。线程访问堆(方法区)中的变量时,先在栈中创建一个变量的副本,修改后再同步到堆中。若是一个线程刚创建副本,这时另外一线程修改了变量,还没有同步到堆中,这时就会出现两个线程操做同一变量的同一种状态的现象,好比i=9,变量i的初始值为9,每个线程的操做都是减1。两个线程A与B同时访问变量,B先执行i-1,在将结果i=8同步到堆中前,A线程也执行i-1,这时i=9的状态就被执行两次,出现线程安全问题。
线程安全问题产生的缘由:一个线程对共享数据的修改不能当即为其余线程所见。spa

volatile提供了一种解决方案:
一旦一个线程修改了被volatile修饰的共享数据,这种修改就会当即同步到堆中,这样其余数据从堆中访问共享数据时始终得到的是在多个线程中的最新值。
volatile的缺陷:线程

volatile只能保证一个线程从堆中获取数据时获取的是当前全部线程中的最新值,假如一个线程已经从堆中复制了数据,在操做完成前,其余线程修改了数据,修改后的数据并不会同步到当前线程中。指针

3.有序性问题

为了提升执行效率,CPU会对那些没有依赖关系的指令从新排序,从新排序后的执行结果与顺序执行结果相同。
例如,在源代码中:htm

int i=0; int y=1;对象

CPU在执行时可能先执行“int y=1;”,接着执行“int i=0;”,执行结果与顺序执行结果相同。
指令重排在单线程环境下是安全的,在多线程环境下就可能出现问题。好比:
线程A:blog

s=new String("sssss");//指令1 flag=false;//指令2

线程B:

while(flag){ doSome(); } s.toUpperCase();//指令3

若是线程A顺序执行,即执行指令1,再执行指令2,线程B的执行不会出现问题。指令重排后,假如线程A先执行指令2,这时flag=true,切换到线程2,终止循环,执行指令3,因为s对象还没有建立就会出现空指针异常。
有序性问题产生的缘由:

一个线程对其余线程对共享数据的修改操做有顺序要求,好比线程B要求线程A先执行指令1,再执行指令2,因为指令重排,实际并未按照要求的顺序执行,这时就出现了线程安全问题。

解决思路:

  1. 利用同步机制,使得同一时间只有一个线程能够访问共享数据,效率低。
  2. 使用volatile,一个指令包含volatile修饰的变量,那么这条指令的执行顺序不变,该指令先后的指令能够各自独立重排,没法交叉重排。
相关文章
相关标签/搜索