多线程存在的问题
多线程运用得好可以大大提高系统的性能。但是使用不当也会对系统造成毁灭性灾难。
- 线程安全问题。多个线程操作共享数据时,会产生线程安全问题。导致读取脏数据或者丢失更新等问题
- 线程活性问题。由于程序问题导致一个线程一直处于非Runnable状态或者处于Runnable状态但执行的任务没有紧张称为线程活性问题。例如:两个线程,线程1需要先占用锁1,再占用锁2。线程2需要先占用锁2,再占用锁1。这是如果线程1占用了锁1,线程2占用了锁2。他们都占用了对方需要的锁,双方都阻塞等待对方的锁释放,导致死锁。
- 上下文切换。线程切换引起的上下文切换,会增加系统消耗。
线程安全问题
线程安全问题是多个线程在操作共享数据引起的。要保证线程安全,就需要保证对共享数据的操作有三个性质:原子性,可见性和有序性。
原子性
原子性是指涉及共享数据的操作对别的线程是不可分割的。即其他线程只能看到该操作未发生或者已经结束。
注意 i++ 并不是原子性操作,i++实际上是一个read-modify-write
操作。
- 先读取出i的值
- 修改i的值
- 写回内存
可见性
可见性是指一个线程对共享数据修改后,其他线程可以看到修改后的值。
导致可见性的原因
- 由于java内存模型中,每个线程都有一个工作内存。在对共享数据进行修改和读取时,
是先对工作内存中的数据进行操作。所以其他线程读取的共享变量可能是脏数据,无法保证可见性。 - 有序性影响可见性。重排序后,一个线程对共享变量的更新对其他线程来说可能变得不可见。如原顺序,操作1->操作2,操作2读取操作1的结果 。重排序后顺序, 操作2->操作1。操作1的结果对操作2是不可见的。
Java内存模型
java内存模型,简称JMM。java线程之间的通信是通过JMM控制的。JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有其工作内存,存有共享变量的副本。线程对共享变量的读写都是先对工作内存进行,工作内存再将共享变量和内存同步。1
工作内存是抽象概念,并非真实存在。它蕴含了缓存,写缓存区,寄存器还有其他硬件等。
有序性
JIT编译器,处理器和存储子系统为了优化系统,会对代码进行重排序。重排序按照as-if-serial语义,保证重排序后在单线程时运行结果是一样的。但是多线程时,无法保证有序性。
代码经过各级重排序优化再最终执行
happens-before
happens-before规则是JMM对多线程重排序的约束规则,遵循happens-before规则的重排序不会改变多线程的执行结果。
1 | int a=1; //A |
A happens-before B(非必须)
A happens-before C
B happens-before C
JMM对happens-before的定义:
- 如果一个操作happens-before另一个操作,那么操作一的执行结果对第二个操作时可见的,并且第一个操作执行顺序在第二个操作之前。
- 两个操作如果存在happens-before规则,并不意味者java平台会按照happens-before的执行顺序执行。如果重排序的执行结果和按happens-before顺序执行的结果一致的话,jmm允许这种排序。
定义1是JMM对程序员的保证.程序员关注结果,最终的执行结果和按执行顺序执行的结果一致。
定义2是JMM对编译器和处理器重排序的约束。
两者并不冲突。
下面是happens-before规则:
- 程序次序规则:一个线程内,按照代码顺序,书写在前面的操作先行发生于书写在后面的操作;
- 锁定规则:一个unLock操作先行发生于后面对同一个锁额lock操作;
- volatile变量规则:对一个变量的写操作先行发生于后面对这个变量的读操作;
- 传递规则:如果操作A先行发生于操作B,而操作B又先行发生于操作C,则可以得出操作A先行发生于操作C;
- 线程启动规则:Thread对象的start()方法先行发生于此线程的每个一个动作;
- 线程中断规则:对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生;
- 线程终结规则:线程中所有的操作都先行发生于线程的终止检测,我们可以通过Thread.join()方法结束、Thread.isAlive()的返回值手段检测到线程已经终止执行;
- 对象终结规则:一个对象的初始化完成先行发生于他的finalize()方法的开始;
happens-before规则也保证了可见性,先执行的操作结果对后面执行的操作是可见的。
happens-before推导1
2
3
4
5
6
7
8
9
10
11private 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 happens-before 2 ; 3 happens-before 4
- 根据volatile规则,2 happens-before 3
- 根据传递性规则,由 1 happens-before 2,2 happens-before 3,3 happens-before 4 => 1 happens before 4
线程安全解决方案
多线程安全问题是因为多个线程同时操作共享变量,缺乏同步机制来协调线程间数据的访问和活动。
- 避免多线程操作共享变量
- 利用jdk提供了锁,volatile关键字等线程同步机制