多线程(四):线程安全
Posted 头发都哪去了
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了多线程(四):线程安全相关的知识,希望对你有一定的参考价值。
多线程(四):线程安全
观察线程安全和线程不安全
观察线程安全
单线程是线程安全的,示例代码如下:
public class ThreadDemo25 {
static class Counter {
//定义私有变量
private int num = 0;
//定义任务执行次数
private final int maxSize = 100000;
//num++
public void incrment() {
for (int i = 0; i < maxSize; i++) {
num++;
}
}
//num--
public void decrment() {
for (int i = 0; i < maxSize; i++) {
num--;
}
}
public int getNum() {
return num;
}
}
public static void main(String[] args) {
Counter counter = new Counter();
counter.incrment();
counter.decrment();
System.out.println("最终执行结果:" + counter.getNum());
}
}
该代码的执行结果如下:
观察线程不安全
多线程可能是线程不安全的,示例代码如下:
public class ThreadDemo26 {
static class Counter {
//定义私有变量
private int num = 0;
//定义任务执行次数
private final int maxSize = 100000;
//num++
public void incrment() {
for (int i = 0; i < maxSize; i++) {
num++;
}
}
//num--
public void decrment() {
for (int i = 0; i < maxSize; i++) {
num--;
}
}
public int getNum() {
return num;
}
}
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
counter.incrment();
});
t1.start();
Thread t2 = new Thread(() -> {
counter.decrment();
});
t2.start();
t1.join();
t2.join();
System.out.println("最终执行结果:" + counter.getNum());
}
}
若是线程安全的话,代码执行结果,应该是与前者一致的,但实际执行结果如下:
线程安全概念
如果多线程环境下代码运行的结果是符合我们预期的,即在单线程环境应该的结果,则说这个程序是线程安全的;反之,这个程序就是线程不安全的。
导致线程不安全的原因
比如刚才我们看到的 num++,其实是由三步操作组成的:
- 从内存把数据读到 CPU
- 进行数据更新
- 把数据写回到 CPU
不保证原子性会给多线程带来的问题:
如果一个线程正在对一个变量操作,中途其他线程插入进来了,如果这个操作被打断了,结果就可能是错误的。
原子性:
我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入房间之后,还没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性的。
一条 java 语句不一定是原子的,也不一定只是一条指令。
不保证原子性会给多线程带来什么问题
如果一个线程正在对一个变量操作,中途其他线程插入进来了,如果这个操作被打断了,结果就可能是错误的。
为了提高效率,JVM在执行过程中,会尽可能的将数据在工作内存中执行,但这样会造成一个问题,共享变量在多线程之间不能及时看到改变,这个就是可见性问题。
示例代码如下:
public class ThreadDemo27 {
private static boolean flag = false;
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while (!flag) {
}
System.out.println("终止执行");
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("设置flag = true");
flag = true;
}
});
t2.start();
}
}
代码执行结果如下:
我们发现,该代码的预期执行结果,应该是当运行flag = true;
代码时,t1线程的while循环条件不满足,跳出循环并执行System.out.println("终止执行");
,但是,由于内存不可见,造成t1线程中while死循环现象。
线程非安全的原因:
- CPU抢占 执行(万恶之源)
- 非原子性,如图示
- (内存)不可见
- 编译器优化(代码优化)
- 多个线程修改了同一个变量
编译器优化:在单线程下执行,没有问题,可以提高程序执行的效率;但是在多线程下就会出现混乱,从而导致线程不安全。
演示:多线程操作并未修改相同变量,那么线程也是安全的,代码如下,
public class ThreadDemo28 {
static class Counter {
//定义任务执行次数
private final int maxSize = 100000;
//num++
public int incrment() {
int num1 = 0;
for (int i = 0; i < maxSize; i++) {
num1++;
}
return num1;
}
//num--
public int decrment() {
int num2 = 0;
for (int i = 0; i < maxSize; i++) {
num2--;
}
return num2;
}
}
private static int num1 = 0;
private static int num2 = 0;
public static void main(String[] args) throws InterruptedException {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
num1 = counter.incrment();
});
t1.start();
Thread t2 = new Thread(() -> {
num2 = counter.decrment();
});
t2.start();
t1.join();
t2.join();
System.out.println("最终执行结果:" + (num1 + num2));
}
}
该代码执行结果如下:
可见,该程序的多线程是安全的。
以上是关于多线程(四):线程安全的主要内容,如果未能解决你的问题,请参考以下文章
JAVA之旅(十三)——线程的安全性,synchronized关键字,多线程同步代码块,同步函数,同步函数的锁是this