线程同步
为什么引入同步机制?
多线程因为存在共享资源,为了保证其原子性,保证线程安全,必须引入同步机制。
一、必要的概念
因为多线程的共享内存,当多线程对共享内存进行操作的时候,就存在两个大问题必须解决:竞态条件、内存可见性
1、竞态条件
当多线程访问和操作同一对象的时候,如果对资源访问的访问顺序敏感,就称存在竞态条件。
例子:
比如线程A、B都从内存拿数据count,A对数据+2,B对数据+3,所以按照我们期待的结果最终写入内存的数据应该是5。但是其实不是这样的,因为当初A、B从内存拿数据count的时候,数据count是0,所以最终count的结果取决于A、B谁后写入内存谁后写入,count就是对应的执行结果。
常用的解决方法:
- 使用synchronized关键字
- 使用显式锁(Lock)
- 使用原子变量
2、内存可见性
关于内存可见性的问题首先要从内存和cpu的配合谈起,内存是一个硬件,执行速度比CPU慢几百倍,所以在计算机中,CPU执行运算的时候,不会每次运算都和内存进行数据交互,而是先把一些数据写入CPU中的缓存区(寄存器和各级缓存),在结束以后再写入内存。这个过程是极其快的,单线程下是没有任何问题的。
但是多线程就出现了问题,一个线程对内存中的一个数据做出了修改,但是并没有及时写入内存(暂时放在缓存中);这时候另一个线程对同样的数据进行修改的时候拿到的就是内存中还没有被修改的数据,也就是说一个线程对一个共享变量的修改,另一个线程不能马上看到,甚至永远看不到。就跟上面的例子也是一样的,不过竞态条件关注的是因为执行顺序而带来的结果的不同,而内存可见性强调的是数据的原子性。
这就是内存可见性问题。
常用的解决方法:
- 使用volatile关键字
- 使用synchronized关键字
- 使用显式锁同步
二、线程同步
synchronized 方法(同步方法)
从java 1.0 版开始,Java中的每个对象都有一个内部锁。如果一个方法用synchronized关键字声明,那么对象的锁将保护整个方法。也就说,要调用该方法,线程必须获得内部的对象锁。避免了程序员使用显式锁的方式lock和unlock。
代码如下:
public class synchronizedTest {
private int money = 100;
public synchronized int getMoney(int number){
if(number < 0){
return -1;
}else if(number > money){
return -2;
}else if(money < 0){
return -3;
}else{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
money -= number;
System.out.println("存款剩余" + money);
return number;
}
}
等价于
public class synchronizedTest {
Lock mLock = new ReentrantLock();
private int money = 100;
public int getMoney(int number){
mLock.lock();
try{
if(number < 0){
return -1;
}else if(number > money){
return -2;
}else if(money < 0){
return -3;
}else{
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}