首先,线程不安全会产生什么问题,我们看下面的代码作为例子
class Count
{
public int count=0;
public void increase()
{
count++;
}
}
public class Demo_eight {
public static Count counter=new Count();
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.count);
}
}
该代码的目的是使用两个线程同时调用class类的increase()方法实现对count进行自增,并将结果打印出来
按道理打印出来的结果应该是100000,但是结果却与我们预期的不同,这就是调度器的随机调度造成的线程不安全。
那么,为何调度器随机调度会造成线程不安全:
首先,count++这段代码是由三条机器语言执行的,分别是LOAD,ADD,SAVE,意思分别是从内存中读取到CPU,在CPU寄存器中完成加法运算,在把内容写回到内存中。程序运行一次count++的过程大概如下图所示:
此时来看是没什么问题的,但是由于调度器的随机调度,LOAD,ADD,SAVE 三条语句的顺序会发生变化,导致计算出错而达不到我们想要的结果。即可得知,线程不安全的其中一个主要原因是调度器的随机调度。
除此之外,线程不安全还有其它原因
1.调度器的随机调度(根本原因)。
2.多个线程同时修改一个变量。
3.操作不是原子的。
4.存在内存的可见性问题。
那么如何解决此类问题呢,我们这里可以使用两个方法:synchnized或者volatile方法
1.synchronized方法
该方法主要用于解决调度器的随机调度问题,用于给线程上锁,如下图所示,给increase()方法加上synchronized之后,若t1先拿到锁,t2就会阻塞等待,等到t1执行完,t2才能拿到锁,就解决了上述的调度器的随机调度问题:
class Count
{
public int count=0;
synchronized public void increase()
{
count++;
}
}
2.volatile方法
volatile 方法主要是用来解决内存的可见性问题,那么首先,什么是内存可见性问题??
内存可见性问题主要是由于操作系统的自动优化造成的,如下图所示:
LOAD进行的是对内存的读操作,但是如果每次读的内容都一样,JVM就会做出优化,省略了后面的LOAD操作,从而产生了内存可见性问题。使用volatile就可以阻止JVM进行这样的优化,从而解决线程安全问题。代码如下图所示:
static class Counter {
public volatile int flag = 0;
}
public static void main(String[] args) {
Counter counter = new Counter();
Thread t1 = new Thread(() -> {
while (counter.flag == 0) {
System.out.println();
}
System.out.println("The circulation is over!");
});
Thread t2 = new Thread(() -> {
Scanner scanner = new Scanner(System.in);
System.out.println("Input a integer:");
counter.flag = scanner.nextInt();
});
t1.start();
t2.start();
}
这样就阻止了JVM对变量flag的优化。从而能使得循环结束。



