感谢
观迎各位网友能抽出宝贵时间来看我的博客,也欢迎各大网友指正我的不足之处!
好了。我们来看一下面试经常会问的线程安全问题。
首先,为什么线程会不安全,意思是,线程不安全的几个原因。我来总结一下。
第一,线程是抢占式执行,线程之间的调度之间充满不确定性,和随机性,这是计算机系统本身来设计的,我们不能改变。(这就是根本原因)。
第二,多个线程对同一变量及进行操作,此处指的操作是(多个线程对同一变量读和写),如果多个变量同时对同一变量都没事,对不同变量写没事。
第三点,针对变量的操作不是原子性的,就是说一个操作还可以分成好几小部分来执行,这个容易出错。
针对前面三点,我们来举一个例子。
让两个线程针对同一变量count=0,自增5000次,你们项的结果可能是10000.但是其实不是的。
沃尔玛能写出代码来展示一下。
public class TestDemo6 {
static int count=0;
public static void main(String[] args) throws InterruptedException {
Thread thread1=new Thread(()-> {
for (int i = 0; i < 5000; i++) {
count++;
}
});
Thread thread2=new Thread(()-> {
for (int i = 0; i < 5000; i++) {
count++;
}
});
//开始执行线程
thread1.start();thread2.start();
//等待线程执行结束
thread1.join();thread2.join();
//打印结果
System.out.println(count);
}
}
执行了三次结果分别是:
but why,为啥呢。原因就在于上面说的三条。
不要小瞧这个count++;他是不满足原子性的,就是说count++,还可以分成3部分操作,来执行。
如上图这么执行操作并没有什么问题,但是我说过线程会发生抢占式执行,所以会出现以下的现象,结果会小于10000;
来看视频讲解。
线程安全问题举例讲解
第四种就是内存可见性,这个名字有点抽象,就是JVM为了优化代码,自行操作,提高运行速度。
比如来举一个例子
public class TestDemo6 {
//=内存可见性
static int isquit=0;
public static void main(String[] args) {
Thread thread=new Thread(){
@Override
public void run() {
while(isquit==0){
;
}
System.out.println("我是一个线程");
}
};
thread.start();
Scanner scanner=new Scanner(System.in);
System.out.println("请输入一个不是0的数来让线程结束");
isquit=scanner.nextInt();
System.out.println("main执行结束");
}
}
结果是;
但是为啥子进程还在运行呢,
所以我们刚才修改了数值以后,子进程并不会因为我们的修改而停止,我们可以判断,这个代码直接从CPU中的寄存器来读取数值,而不是从内存。
还有一种就是,指令重排序!
所以为了防止这种情况,我们还是有办法解决这中问题的。
怎么保护线程的安全
举例演示
static int count=0;
synchronized public static void func(){
count++;
}
public static void main(String[] args) throws InterruptedException {
Thread thread1=new Thread(()-> {
for (int i = 0; i < 5000; i++) {
func();
}
});
Thread thread2=new Thread(()-> {
for (int i = 0; i < 5000; i++) {
func();
}
});
thread1.start();thread2.start();
thread1.join();thread2.join();
System.out.println(count);
}
}
我们给这个func方法加锁,使用synchronaized,关键字来修饰,保证每次只能一个线程来使用。在每个count++;前面加锁,一个线程正在执行,那么另一个线程,因为正在执行的线程有锁,那就不能抢占了。