造成线程不安全的几种因素
因素①: 抢占时执行
(多个线程的调度执行的过程中是"全随机"的,实质上并不是纯随机,但是在应用层程序这里是没有规律的)
例如:
public static void main(String[] args) {
Thread t = new Thread(() -> {
while (true){
System.out.println("这是一个新线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
});
t.start();
while (true){
System.out.println("主线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
上述代码所产生的结果我们所预期的应该是一直循环先打印 "这是一个新线程"然后打印 "主线程"
但其产生的结果却与我们预期不同,这就是抢占时执行所造成的线程不安全
结果如图:
因素②: 多个线程修改同一变量
例如:
class Counter{
public int count = 0;
public void increase(){
count++;
}
}
public class Sep14thdemo3 {
private static Counter counter = new Counter();
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
counter.increase();//线程t所执行的对count进行修改的操作
}
},"线程t");
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
counter.increase();//线程t1所执行的对count进行修改的操作
}
},"线程t1");
t.start();
t1.start();
t.join();
t1.join();
System.out.println("count=" + counter.count);
}
}
//此时线程t与线程t1这两个不同的线程同时对一个相同的变量count进行了修改,造成了线程不安全
上述代码中我们所预期的结果应该为20000单当代码执行后所产生的结果却与我们预期中的结果有较大的差别,其原因就是因为多个线程修改同一变量,并且每次产生的结果都是不同的.
结果如图:
因素③: 修改操作不是原子的
(其中原子是指不可分割的最小单位)
例如:
public static void main(String[] args) {
int count = 0;
while(true){
count++;//这里的count++操作实质上是三个CPU指令分别为 load, add, save. 这三个指令
//其中CPU执行指令都是以一个指令为最小单位的不存在一个指令执行到一半就被调度走了的情况.
//如果说CPU再执行count++操作时刚执行完load指令之后就被调度走了就会出现线程不安全的情况
//所以像count++这样的分为多个指令的操作就不能成为原子操作
//反之像int count = 0 这样的操作就是只有一个指令的原子操作就会相对与非原子操作更为安全
}
}
因素④: 内存可见性问题
因素⑤: 指令重排序
解决线程不安全问题
解决上述问题一般解决的方法是通过特殊手段讲count++这种非原子操作变成原子的
例如:
class Counter{
public int count = 0;
public synchronized void increase(){//通过加锁的方式来让其变成原子操作
count++;
}
}
public class Sep14thdemo1 {
private static Counter counter = new Counter();
public static void main(String[] args) {
Thread t = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
counter.increase();
}
});
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
counter.increase();
}
});
t.start();
t1.start();
try {
t.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
try {
t1.join();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("count=" + counter.count);
}
}