1.原因
(1)抢占式执行(罪魁祸首): 多个线程调度执行时,可以视为是"全随机"的(也不能理解为是纯随机,但是这个在应用层是没有规律的).
(2)多个线程修改同一个变量.
(3)修改操作不是原子的(原子指不可分割的最小单位).
理解:像count++一种操作,本质上是三个CPU指令(load,add,save),一个CPU执行指令都是以一个指令为单位进行执行,一个指令就相当于CPU上的"最小单位",不能还没执行完就被调度走了.
(4)内存可见性问题(JVM的代码优化引入的bug)
(5)指令重排序
2.解决方案
(1)加锁(使操作变成原子的)
理解:在count++之前加锁,在count++以后再解锁,在加锁与解锁之间不允许别的线程对count进行修改.
这个方案要使用synchronized来实现:
class Counter{
public int count = 0;
public synchronized void increase() {
count++;
}
}
public class Demo1 {
private static Counter counter = new Counter();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("counter: " + counter.count);
}
}
使用synchronized之前,每次运行得到的值不确定,使用后得到的值都是确定的10万.
加锁之后线程的执行图:
1.t1.t2表示两个线程
2.加了synchronized后就相当于在线程开头加了一个LOCK操作,在线程后头加了一个UNLOCK操作.
3.如果t1执行了LOCK,那么t2就会卡在LOCK上堵塞等待,直到t1执行完UNLOCK后,t2才会有机会执行后面的
4.可以理解为把"并发"变成"串行",但是这样会减慢执行效率.
5.要知道,加锁并不是说CPU一鼓作气执行完,中间可能会有调度切换,即使t1被切换了,t2仍然是LOCK状态,不能在CPU上执行(就像你在付费自习室中选中了A1这个座位并付了2个小时的钱,如果中间你出去看热闹去了,但是时间还没到,别人是不能占用你的位子的).
问题:一个线程加锁,一个线程不加锁,这个时候会咋样?线程安全是否能保证?
如果上面那个代码,弄两个increase方法,一个加锁,一个不加锁,分别让t1,t2来调用,我们会发现结果不确定了.
所以说,线程安全不是说加了锁就是安全的了,而是通过加锁来把并发修改同一个变量变成串行修改同一个变量,这样才安全,不确定的加锁姿势不一定能解决线程安全问题.
如果要加锁的代码,不是在一个方法中那么我们可以用synchronized来修饰代码块
class Counter{
public int count = 0;
public void increase() {
//括号里要填加锁的对象.
//我这里填的是this,意思是谁调用这个方法,谁就是这个对象
//大部分情况可以用this作为锁对象
synchronized(this) {
count++;
}
}
public void increase2() {
count++;
}
}
public class Demo1 {
private static Counter counter = new Counter();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 50000; i++) {
counter.increase();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("counter: " + counter.count);
}
}