java多线程如何保证线程安全,Java线程安全问题

  java多线程如何保证线程安全,Java线程安全问题

  00-1010 1.什么是线程安全和线程不安全?2.为什么自增量操作不是线程安全的?3.关键领域的资源和竞争条件概要:面试题:

  什么是线程安全和线程不安全?自增量操作是线程安全的吗?多线程下如何保证正确的I结果?

  00-1010什么是线程安全?当多个线程并发访问一个Java对象时,无论系统如何调度这些线程,或者这些线程将如何交替操作,该对象都可以显示一致和正确的行为,因此对该对象的操作是线程安全的。

  如果这个对象表现出不一致和错误的行为,那么这个对象的操作就不是线程安全的,线程安全问题就出现了。

  00-1010线程安全实验:两个线程对初始值为0的静态变量做自增自减,各做5000次。结果是0吗?具体代码如下

  公共类thread demo { private static int I=0;公共静态void main (string [] args)抛出中断异常{//线程1对变量I执行5000次自增操作Thread t1=new Thread(()-{ for(int j=0;j5000j){ I;} });线程t2=新线程(()-{ for(int j=0;j5000j){ I-;} });t1 . start();T2 . start();//主线程等待t1线程和t2线程执行完毕,再继续执行t1 . join();T2 . join();system . out . println(I);//581/-1830/0 }}以上结果可能是正的,也可能是负的,也可能是零。为什么?因为Java中静态变量的自增自减都不是原子操作,所以要想彻底理解就要从字节码中分析。

  例如对于 i++ 而言,实际会产生如下的 JVM 字节码指令:

  Getstatic i //获取静态变量I iconst_1 //准备常数1 add//自增putstatic i //将修改后的值存储在静态变量I而对应 i-- 也是类似:.中

  Getstatic i //获取静态变量I的值iconst_1 //准备常数1 sub//减量putstatic i //将修改后的值存储在静态变量中。Java的内存模型如下。为了完成静态变量的自增,自减需要主存和工作内存之间的数据交换:

  如果是单线程,上面8行代码顺序执行(不交错),没有问题:

  但多线程下这 8 行代码可能交错运行:

  在负数的情况下:

  在正数的情况下:

  因此,自动递增运算符是一个复合运算,它至少包括三个JVM指令:“从内存中获取值”、“将寄存器递增1”和“将值保存到内存中”。这三条指令在JVM内部是独立执行的,完全有可能中间会并发执行多个线程。这三个JVM指令,即“从内存中取值”、“将寄存器加1”和“将值存入内存”,是不可分的。它们是原子的和线程安全的,也称为原子操作。然而,当两个或多个原子操作被组合时,它们就不再是原子的了。比如写前读,有可能读后,其实这个变量被修改了,导致读写数据不一致。

  00-1010当多个线程操作相同的资源(如变量、数组或对象)时,可能会出现线程安全问题。一般来说,只有当多个线程写入该资源时,才会出现问题。如果是简单的不改变资源的读操作,显然不会有问题。

  临界区资源代表了一种可以被多重化的资源。

  个线程使用的公共资源或共享数据,但是每一次只能有一个线程使用它。一旦临界区资源被占用,想使用该资源的其他线程则必须等待。在并发情况下,临界区资源是受保护的对象。

  临界区代码段是每个线程中访问临界资源的那段代码,多个线程必须互斥地对临界区资源进行访问。线程进入临界区代码段之前,必须在进入区申请资源,申请成功之后执行临界区代码段,执行完成之后释放资源。临界区代码段的进入和退出如图所示:

  

 

  竞态条件可能是由于在访问临界区代码段时没有互斥地访问而导致的特殊情况。如果多个线程在临界区代码段的并发执行结果可能因为代码的执行顺序不同而不同,我们就说这时在临界区出现了竞态条件问题。

  比如下面代码中的临界区资源和临界区代码段:

  

public class SafeDemo { // 临界区资源 private static int i = 0; // 临界区代码段 public void selfIncrement(){ for(int j=0;j<5000;j++){ i++; } } // 临界区代码段 public void selfDecrement(){ for(int j=0;j<5000;j++){ i--; } }// 这个不是临界区代码,因为虽然使用了共享资源,但是这个方法并没有被多个线程同时访问 public int getI(){ return i; }}
public class ThreadDemo { public static void main(String[] args) throws InterruptedException { SafeDemo safeDemo = new SafeDemo(); Thread t1 = new Thread(()->{ safeDemo.selfIncrement(); }); Thread t2 = new Thread(()->{ safeDemo.selfDecrement(); }); t1.start(); t2.start(); t1.join(); t2.join(); System.out.println(safeDemo.getI()); }}

当多个线程访问临界区的selfIncrement()方法时,就会出现竞态条件的问题。更标准地说,当两个或多个线程竞争同一个资源时,对资源的访问顺序就变得非常关键。为了避免竞态条件的问题,我们必须保证临界区代码段操作具备排他性。这就意味着当一个线程进入临界区代码段执行时,其他线程不能进入临界区代码段执行。

 

  

 

  

总结:

(1) 一个程序运行多个线程本身是没有问题的,问题出在多个线程访问共享资源,多个线程读共享资源其实也没有问题,而在多个线程对共享资源读写操作时发生指令交错,就会出现问题 ;

 

  (2) 一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区代码块;

  (3) 多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件;

  在Java中,可以使用synchronized关键字,使用Lock显式锁实例,或者使用原子变量(AtomicVariables)对临界区代码段进行排他性保护。

  本篇文章就到这里了,希望能够给你带来帮助,也希望您能够多多关注盛行IT的更多内容!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: