线程安全
多线程的n++和n–之后
public class ThreadDemo {
private static class Counter{
private long n=0;
public void increment(){
n++;
}
public void decrement(){
n--;
}
public long value(){
return n;
}
}
public static void main(String[] args) throws InterruptedException {
final int COUNT=1000_0000;
Counter counter=new Counter();
Thread thread=new Thread(()->{
for (int i = 0; i < COUNT ; i++) {
counter.increment();
}
},"小代");
thread.start();
for (int i = 0; i <COUNT ; i++) {
counter.decrement();
}
thread.join();
//期望最终结果应该是0
System.out.println(counter.value());
}
}
线程安全的概念:
如果多线程环境下运行的结果是符合预期的,就是在单线程环境下运行的结果,则说明这个线程是安全的。
线程不安全的原因:
1.原子性:
我们把一段代码想象成一个房间,每个线程就是要进入这个房间的人。如果没有任何机制保证,A进入房间之后,还
没有出来;B 是不是也可以进入房间,打断 A 在房间里的隐私。这个就是不具备原子性的。
那我们应该如何解决这个问题呢?是不是只要给房间加一把锁,A 进去就把门锁上,其他人是不是就进不来了。这样
就保证了这段代码的原子性了。
有时也把这个现象叫做同步互斥,表示操作是互相排斥的。
2、可见性:
先了解内存
为了提高效率,JVM在执行过程中,会尽可能的将数据在工作内存中执行,但这样会造成一个问题,共享变量在多线
程之间不能及时看到改变,这个就是可见性问题。
3、代码的顺序性
什么是代码重排序
一段代码是这样的:
- 去前台取下 U 盘
- 去教室写 10 分钟作业
- 去前台取下快递
如果是在单线程情况下,JVM、CPU指令集会对其进行优化,比如,按 1->3->2的方式执行,也是没问题,可以少跑
一次前台。这种叫做指令重排序。
public class ThreadDemo2 {
private static class Counter{
private int n1=0;
private int n2=0;
private int n3=0;
private int n4=0;
private int n5=0;
private int n6=0;
private int n7=0;
private int n8=0;
private int n9=0;
private int n10=0;
public void write(){
n1=1;
n2=2;
n3=3;
n4=4;
n5=5;
n1=6;
n2=7;
n3=8;
n4=9;
n5=10;
}
public void read(){
System.out.println("n1="+n1);
System.out.println("n2="+n2);
System.out.println("n3="+n3);
System.out.println("n4="+n4);
System.out.println("n5="+n5);
System.out.println("n6="+n6);
System.out.println("n7="+n7);
System.out.println("n8="+n8);
System.out.println("n9="+n9);
System.out.println("n10="+n10);
}
}
public static void main(String[] args) {
Counter counter=new Counter();
Thread thread1=new Thread(()->{
counter.read();
},"读");
Thread thread2=new Thread(()->{
counter.write();
},"写");
thread1.start();
thread2.start();
}
}
/*
执行结果:
n1=6
n2=7
n3=8
n4=9
n5=10
n6=0
n7=0
n8=0
n9=0
n10=0
*/
代码重排序会给多线程带来什么问题
刚才那个例子中,单线程情况是没问题的,优化是正确的,但在多线程场景下就有问题了,什么问题呢。可能快递是
在你写作业的10分钟内被另一个线程放过来的,或者被人变过了,如果指令重排序了,代码就会是错误的。
解决线程不安全的问题
//解决线程不安全的问题,给每个方法之前都加上synchronized 关键字,程序运行结果就会正确
public class ThreadDemo3 {
private static class Counter{
private long n=0;
public synchronized void increment(){
n++;
}
public synchronized void decrement(){
n--;
}
public synchronized long value(){
return n;
}
}
public static void main(String[] args) throws InterruptedException {
final int COUNT=1000_0000;
Counter counter=new Counter();
Thread thread=new Thread(()->{
for (int i = 0; i < COUNT ; i++) {
counter.increment();
}
},"代");
thread.start();
for (int i = 0; i <COUNT ; i++) {
counter.decrement();
}
thread.join();
//期望最终结果应该是0
System.out.println(counter.value());
}
}
/*
执行结果:0
*/