java线程安全问题的原因,java线程不安全会造成什么
00-1010 1.什么是线程安全2。线程不安全的原因1。修改共享数据2。原子性3。内存可见性4。重新排序说明3。解决线程安全方案。
00-1010给线程安全下一个准确的定义是很复杂的,但是我们可以这样想:
如果代码在多线程环境下运行的结果符合我们的预期,也就是单线程环境下的预期结果,那么这个程序就是线程安全的。
目录
一、什么是线程安全
静态类计数器{ public int count=0;void increase(){ count;} } public static void main(String[]args)抛出interrupted exception { final Counter Counter=new Counter();线程t1=新线程(()-{ for(int I=0;我50000;I){ counter . increase();} });线程t2=新线程(()-{ for(int I=0;我50000;I){ counter . increase();} });t1 . start();T2 . start();t1 . join();T2 . join();system . out . println(counter . count);}在上面的线程不安全代码中,多个线程参与修改counter.count变量。此时,这个counter.count是一个可以被多个线程访问的“共享数据”。
00-1010原子性是为了提供互斥访问。同一时间只有一个线程可以操作数据。有时候这种现象叫做同步互斥,也就是说操作是互斥的。
不保证原子性给多线程带来了哪些问题?如果一个线程正在对一个变量进行操作,中间插入了其他线程,如果这个操作被中断,结果可能是错误的。这也与线程的抢占式调度密切相关。如果线程不是“抢占式”的,即使没有原子性,也没什么大问题。
00-1010可见性是指一个线程对共享变量值的修改可以被其他线程及时看到。
Java内存模型(JMM): Java虚拟机规范定义了Java内存模型,旨在屏蔽各种硬件和操作系统的内存访问差异,从而达到Java程序在各种平台上一致并发的效果。
私有静态int count=0;公共静态void main (string [] args)引发中断的异常{ thread t1=new thread(()-{ while(count==0){ } system . out . println(thread . current thread()。getname() completion );});t1 . start();Scanner scanner=新扫描仪(system . in);system . out . print(-);count=scanner . nextint();}
00-1010一个线程观察其他线程中指令执行的顺序,观察结果一般会因为指令重新排序而出现紊乱(发生在之前原则)。
以编译器指令重新排序为前提
“保持逻辑不变”。这在单线程环境下很容易判断,但在多线程环境下就没那么容易了。多线程代码执行更加复杂,编译器在编译阶段很难预测代码的执行效果。因此,激进的重新排序很容易导致优化后的逻辑像以前一样不等价。
二、线程不安全的原因
volatile解决了内存可见性和指令重新排序生成问题
码在写入 volatile 修饰的变量的时候:
改变线程⼯作内存中volatile变量副本的值,将改变后的副本的值从⼯作内存刷新到主内存
直接访问工作内存,速度快,但是可能出现数据不⼀致的情况加上 volatile , 强制读写内存. 速度是慢了, 但是数据变的更准确了代码示例:
/** * 内存可见性 * 线程1没感受到flag的变化,实际线程2已经改变了flag的值 * 使用volatile,解决内存可见性和指令重排序 */public class ThreadSeeVolatile { //全局变量 private volatile static boolean flag = true; public static void main(String[] args) { //创建子线程 Thread t1 = new Thread(() ->{ System.out.println("1开始执行:" + LocalDateTime.now()); while(flag){ } System.out.println("2结束执行" + LocalDateTime.now()); }); t1.start(); Thread t2 = new Thread(() ->{ //休眠1s try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("修改flag=false"+ LocalDateTime.now()); flag = false; }); t2.start(); }}
volatile的缺点
volatile 虽然可以解决内存可见性和指令重排序的问题,但是解决不了原子性问题,因此对于 ++ 和 --操作的线程非安全问题依然解决不了
通过synchronized锁实现原子性操作JDK提供锁分两种:
①一种是synchronized,依赖JVM实现锁,因此在这个关键字作用对象的作用范围内是同一时刻只能有一个线程进行操作;
②另一种是LOCK,是JDK提供的代码层面的锁,依赖CPU指令,代表性的是ReentrantLock。
synchronized 会起到互斥效果, 某个线程执行到某个对象的synchronized 中时, 其他线程如果也执行到同一个对象 synchronized 就会阻塞等待.进入 synchronized 修饰的代码块, 相当于 加锁退出 synchronized 修饰的代码块, 相当于 解锁synchronized修饰的对象有四种:
(1)修饰代码块,作用于调用的对象
(2)修饰方法,作用于调用的对象
(3)修饰静态方法,作用于所有对象
(4)修饰类,作用于所有对象
// 修饰一个代码块: 明确指定锁哪个对象 public void test1(int j) { synchronized (this) { } } // 修饰一个方法 public synchronized void test2(int j) { } // 修饰一个类 public static void test1(int j) { synchronized (SynchronizedExample2.class) { } } // 修饰一个静态方法 public static synchronized void test2(int j) { }
到此这篇关于深入探究Java线程不安全的原因与解决的文章就介绍到这了,更多相关Java线程不安全内容请搜索盛行IT以前的文章或继续浏览下面的相关文章希望大家以后多多支持盛行IT!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。