在本章节中采用实例+图片的方式,以一个学习者的姿态进行描述问题+解决问题,更加清晰明了,以及过程中会发问的问题都会一一进行呈现
目录
- 线程安全
- 演示线程不安全情况
- 图片解释:
- 将上述代码进行修改【从并行转化成穿行的方式】
- 不会出现问题的可能
- 埋坑问题
- 总结(线程安全问题产生原因)
- 如何解决线程安全问题
- 1.根本原因:
- 2.多线程同时修改一个变量
- 3.修改操作,不是原子的
- 注意:
线程安全
概念:一段代码在多线程并发执行的情况下,出现bug的情况(实际结果与预期结果不符合),预期结果:一般是由别人来预期,此时这种情况是“线程不安全”
演示线程不安全情况
eg:(如果我们需要计算一个20000;两个线程每个线程执行10000次看其的一个情况)
public class Test {
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for (int i = 0; i < 10000; i++) {
count++;
}
});
Thread t2 = new Thread(()->{
for (int i = 0; i < 10000; i++) {
count++;
}
});
t1.start();
t2.start();
Thread.sleep(1000);
System.out.println(count);
}
}
最终运行的结果为:
对代码进行解释:
其中
Thread t1 = new Thread(()->{
for (int i = 0; i < 10000; i++) {
count++;
}
});
对应的是3个cpu的操作:
- load:将内存中的count加载到寄存器上
- add:把寄存器中的内容进行+1;
- save:把寄存器当中的内容保留在内存上
所以最终会出现这样的情况
图片解释:
将上述代码进行修改【从并行转化成穿行的方式】
//只需要使用join方法就可以将并行执行的方式改为串行的方式
public class Test {
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for (int i = 0; i < 10000; i++) {
count++;
}
});
Thread t2 = new Thread(()->{
for (int i = 0; i < 10000; i++) {
count++;
}
});
t1.start();
t1.join();
t2.start();
t2.join();
Thread.sleep(1000);
System.out.println(count);
}
}
最终的结果为:20000
此时上述的反应就被称为——“线程不安全”
不会出现问题的可能
- 如果线程重复的次数少:其运行的速度非常快,会出现一个线程执行完了,另一个线程还没有开始执行——结果就是正确的
eg:
public class Test {
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
for (int i = 0; i < 10; i++) {
count++;
}
});
Thread t2 = new Thread(()->{
for (int i = 0; i < 10; i++) {
count++;
}
});
t1.start();
//t1.join();
t2.start();
//t2.join();
Thread.sleep(1000);
System.out.println(count);
}
}
此时的结果就是正确的;
由于他的速度非常快,在状态转化的过程当中前一个线程就已经完成了操作
埋坑问题
- 在上述的代码当中是否可能出现最终打印的结果<5000
答:可能会出现
解释图片:
在上面的情况中会出现最终打印的1 - 那由上面问题 ,是否最终在我们之前写的代码中出现打印1的情况
答:这个是不太可能的
因为执行的次序是抢占资源的方式,在这个过程中,很少可能会出现4999都只由一个线程执行,然后由两一个线程进行收尾
总结(线程安全问题产生原因)
- 根本原因:操作系统对线程的调度是随机的,抢占式执行的方式
- 多个线程同时修改同一个变量
以下是不会出现问题的情况:
一个线程修改一个变量
多个线程不同时修改同一个变量
多个线程修改不同变量
多个线程读取同一变量 - 修改操作不是原子的
进行解释:如果修改操作只对应一个cpu质量——此时原子的(例如没有多线程的时候,java当中的main线程就不会发生任何的问题)
如何解决线程安全问题
将他产生的原因尽行打破
1.根本原因:
这个是操作系统底层的设定,不能进行改变
或者自己写一个操作系统:两大难点1》技术上会非常难2》推广上会更加难
2.多线程同时修改一个变量
解决方法:调整代码结构【但是这种方法并不是通用的】
3.修改操作,不是原子的
这个是java当中解决线程安全问题,最主要的解决方案
加锁
关键字:synchronized
synchronized(加锁对象){——加锁操作
执行相关代码
}——解锁操作
加锁操作,不是将整个线程锁死在cpu上,禁止这个线程被其他的调度走,但是禁止其他线程重新加这个锁,避免其线程成的操作
java当中可以使用这个关键字修饰任何对象
只有两个线程针对同一个对象加锁操作,才会产生互斥效果
锁对象并不会影响这个对象其他方面的使用
结果展示:
public class Test2 {
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
Thread t1 = new Thread(()->{
synchronized (o) {
for (int i = 0; i < 10; i++) {
count++;
}
}
});
Thread t2 = new Thread(()->{
synchronized (o) {
for (int i = 0; i < 10; i++) {
count++;
}
}
});
t1.start();
//t1.join();
t2.start();
//t2.join();
Thread.sleep(1000);
System.out.println(count);
}
}
上述代码的最终的结果就可以被正确打印
对代码进行的解释:
注意:
在此过程中,只有针对同一个对象加锁才会有效果
在一般情况下:synchronized是对this进行加锁
当synchronized修饰static修饰的变量时,相当于针对类对象尽心的加锁操作
下一张会讲到死锁的问题,不要走开哦!!!!