个人网站 域名,泉州做网站哪家好,公众信息帮竞彩网站做维护吗,稿定设计简单好用的在线设计平台文章目录 一个线程不安全的案例造成线程不安全的原因抢占式执行多个线程修改同一个变量修改操作不是原子的内存可见性问题指令重排序问题 如何让线程变得安全#xff1f;加锁synchronized volatile 一个线程不安全的案例 
题目#xff1a;有较短时间让变量count从0加到10_000… 文章目录 一个线程不安全的案例造成线程不安全的原因抢占式执行多个线程修改同一个变量修改操作不是原子的内存可见性问题指令重排序问题 如何让线程变得安全加锁synchronized volatile  一个线程不安全的案例 
题目有较短时间让变量count从0加到10_0000 
解决方案我们创建两个线程分别让count加5_0000次 
结果count  10_0000 class Count{public int count  0;public void increase(){count;}}
public class Demo {//验证线程不安全问题public static void main(String[] args) {Count count1  new Count();// 操作同一个变量Thread thread1  new Thread(() - {for (int i  0; i  50000; i){count1.increase();}});Thread thread2  new Thread(() - {for (int i  0; i  50000; i){count1.increase();}});thread1.start();thread2.start();try {thread1.join();} catch (InterruptedException e) {e.printStackTrace();}try {thread2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(count1.count);   //  100000}
}造成线程不安全的原因 
抢占式执行 
操作系统调度线程的时候是一个“随机”的过程当两个线程都参与调度的时候谁先谁后不确定。 
多个线程修改同一个变量 
线程之间是“并发执行的”当多个线程修改同一个变量时多个线程同时获取到了变量值。某一个线程修改了变量修改的结果不能被其他线程知道其他线程还会修改原先获取到的值导致结果错误。 
如果修改的是不同的变量线程之间独立执行不会出现问题。 
修改操作不是原子的 
count操作底层是三条指令在CPU上完成的 
load把内存中的值读到CPU寄存器中 addcount1 save把寄存器的值写回内存 
由于这三条指令不是原子的两个线程在执行时就会有不同的执行顺序  在这些执行顺序下都会使count没有正确的使最终结果出错。 
内存可见性问题 
JVM优化引入的BUG。例如两个线程在操作同一个变量一个线程读并且比较一个线程修改。假设读操作非常频繁的情况下比较操作也会非常的频繁。但是读是从内存中读比较是在CPU里比较。比较的速度远远大于读的速度。而且每次读到的值还一样这时编译器就会大胆优化只读取一次后面就不从内存中读了。每次比较都和前面读取到的值比较不和内存中的值比较。这时另一个线程把内存中的值修改了但是这个线程比较的还时原来的值就会有问题。 
指令重排序问题 
JVM优化引入的BUG。由我们自己写的代码在大多数情况下的执行流程中指令的执行顺序往往都不是最优选择即没有使运行速度达到最快。因此JVM在编译时就会在逻辑等价的前提下对我们的指令进行重新排序使代码的运行速度变快。 
这样的优化在单线程时是没有问题的。但是在多线程的情况下线程之间是抢占式执行的哪条指令先执行哪条指令后执行不确定就可能有问题。 
如何让线程变得安全 
“抢占式执行”是线程调度的基本方式我们无法干预。 
“多个线程修改同一个变量”我们在特定场景下就是得修改同一个变量也无法改变。 
“操作不是原子的”我们保证线程安全的主要方式通过synchronized加锁。 
“内存可见性”“指令重排序”JVM优化的问题。使用volatile解决 
加锁 
锁具有独占的特性。如果当前锁还没有被加上加锁操作就能成功如果锁已经被人加上了加锁操作就不能成功会进入阻塞等待的状态。即加锁操作会让”并发执行“变成”串行执行“ 注 处于同一个规则所有线程都会加锁的情况下只有获取到锁才能执行相关操作。一个线程加锁另一个线程不加锁。这样不能保证线程安全。 因为它俩没有采用同一个规则办事儿不会产生锁竞争。 synchronized 
加锁操作是通过synchronized关键字来实现的只要对同一个对象加锁就会产生“锁竞争”。而synchronized有三种用法 
synchronized修饰代码块 
synchronized修饰代码块时必须指定锁对象。如果锁对象是this即对当前对象加锁。 像下面的代码count调用了increase就是对count加锁对同一个对象加锁会出现“锁竞争” public void increase(){synchronized (this){count;}}Count count  new Count();Thread thread1  new Thread(() - {for (int i  0; i  50000; i){count.increase();}});Thread thread2  new Thread(() - {for (int i  0; i  50000; i){count.increase();}});当两个线程针对两个对象加锁时不会产生“锁竞争”不会阻塞等待。 public void increase(){synchronized (this){count;}}Count count1  new Count();Count count2  new Count();Thread thread1  new Thread(() - {for (int i  0; i  50000; i){//对count1加锁count1.increase();}});Thread thread2  new Thread(() - {for (int i  0; i  50000; i){//对count2加锁count2.increase();}});synchronized修饰普通方法 
synchronized修饰普通方法时相当于是锁this对象只要是同一个对象调用到这个方法都会产生“锁竞争”。 public synchronized void increase(){count;}Count count  new Count();Thread thread1  new Thread(() - {for (int i  0; i  50000; i){ count.increase();}});Thread thread2  new Thread(() - {for (int i  0; i  50000; i){count.increase();}});synchronized修饰静态方法 
静态方法是随着类加载而加载的而且只加载一次所以静态方法是和类绑定在一起的只有一份。 public static Object locker  new Object();public void increase(){synchronized(locker){count;}}Count count  new Count();Count count1  new Count();//count和count1都是Count类的对象  里面的静态方法是同一个。//所以它俩虽然是不同的对象  但是也会产生“锁竞争”Thread thread1  new Thread(() - {for (int i  0; i  50000; i){ count.increase();}});Thread thread2  new Thread(() - {for (int i  0; i  50000; i){count1.increase();}});Count.class是对类对象加锁而类也只有一份儿。静态方法也是只有一份儿所以它们俩也会产生“锁竞争” public static Object locker  new Object();public void increase(){synchronized(locker){count;}}public void increase1(){synchronized(Count.class){count}}Count count  new Count();Count count1  new Count();//count和count1都是Count类的对象 类只有一个静态方法也是只有一个。它们俩绑定在一起//所以它俩虽然是不同的对象  但是也会产生“锁竞争”Thread thread1  new Thread(() - {for (int i  0; i  50000; i){ count.increase();}});Thread thread2  new Thread(() - {for (int i  0; i  50000; i){count1.increase();}});volatile public volatile int count  0;volatile只有一个用法就是修饰变量表示该变量的值必须从内存中读取不能从缓存中读取。 即volatile禁止了编译器优化避免了直接读取CPU寄存器中缓存的数据而是每次都读取内存。 
但是volatile并不能保证是操作是原子性的因此它只适合用于一个线程读一个线程修改的场景。不适合用于两个线程都修改的场景。 
谈到volatile就会联想到JMMJava Memory ModelJava内存模型。 
在JMM中引入了新的术语 工作内存work memory即CPU寄存器缓存 主内存(main memory)真正读取的内存 
站在JMM的角度看待volatile 正常程序的执行过程中先会把主内存的数据加载到工作内存中再进行计算处理。编译器优化可能会导致不是每次都会真正的读取主内存而是直接读取工作内存中的缓存数据就可能导致内存可见性问题。volatile起到的效果就是保证每次读取数据都是真的从主内存中重新读取。