如何保证线程安全
在多线程编程中要时刻保持着三个核心的概念
- 原子性:
原子是不可分的,原子性通俗的讲就是一次操作包含有多个步骤,这些步骤要么全部生效,要么全都无效。这一点和数据库事物的原子性概念差不多。 - 可见性:
当多个线程并发访问同一个共享变量时,一个线程对共享变量的修改,其他线程能够立即看到。 - 顺序性:
顺序性,指的是按照代码的先后顺序执行。但实际上JVM真正的在执行代码时,并不会保证完全按顺序执行,它为了提高执行效率会对代码进行优化,按照更高效的顺序执行。但它会保证程序最终的执行结果和按代码顺序执行时的结果保持一致的。
原子性
常用的方法便是锁和同步代码块。
锁
使用锁,保证同一时间只有一个线程可以使用得到锁,保证一个线程能够执行申请锁和释放锁之间的代码。
public void testLock(){
lock.lock();
try {
int a = 1 ;
int b = 2 ;
int c = a + b ;
}finally {
lock.unlock();
}
}
为了确保可以释放锁,需要把lock.unlock()放到finally中
同步代码块
和锁类似,都是实现资源的排他性,同一时间只会被一个线程执行,保证了代码段的原子性。同时也牺牲了性能。同步代码块使用非静态同步方法时,锁住的是当前实例,使用静态同步方法时,锁住的是该类的Class对象。
public void testLock(){
Object object = new Object();
synchronized (object){
int a = 1 ;
int b = 2 ;
int c = a + b ;
}
}
Atomic
https://blog.csdn.net/zhangerqing/article/details/43057799
可见性
Java提供了volatile保证了可见性。使用volatile修饰变量时,对该变量的修改会立刻更新到内存,其他线程读取时就会获得最新的值。
顺序性
JVM通过happens-before原则隐性地保证顺序性。hapens-before原则:
- 传递规则:动作1发生在动作2之前,动作2发生在动作3之前,则动作1肯定发生在动作3之前。
- 锁定规则:unlock肯定会在下一个对同一个锁的lock之前发生。
- volatile原则:对被volatile修饰的写操作发生在后面对这个变量的读操作之前。
- 程序次序规则:同一个线程内,按代码顺序执行
- 线程启动规则:Thread对象的start方法比该线程所有动作都先发生。
- 线程终结原则:线程终止检测最后发生。
- 线程中断规则: 对线程interrupt()方法的调用先发生于对该中断异常的获取。
- 对象终结规则:一个对象构造先于它的finalize发生。
拓展sleep和wait区别
wait是object类的方法,会释放锁,让出cpu,可以使用notify或notifyAll方法激活,再次获得锁便可以继续执行。sleep是线程静态方法不会释放锁。
后续补充。。。。