暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

java线程学习笔记(三)——线程安全概念

辉哥的菜 2021-09-06
697

定义:

当多个线程访问某个类时,不管运行时环境采用何种调度方式 或者这些线程如何交替执行,并且在主调代码中不需要任何额外的同步或者协同,这个类都能表现正确的行为,那么称这个类时线程安全的。

线程安全性主要体现在三个方面:

(1)原子性:即不可分割,提供互斥访问,同一时刻只能有一个线程对它进行操作

(2)可见性:一个线程对共享变量的修改,可以及时被其他线程观察到

(3)有序性:序在执行的时候,程序的代码执行顺序和语句的顺序是一致的。

下面还是直接代码演示 下安全的基本体现:

    public class ThreadSafeQuestions implements Runnable{
    private int value;
    public int getNext(){
    return value++;
    }


        public static void main(String[] args) {
    ThreadSafeQuestions runImpl = new ThreadSafeQuestions();
    Thread thread11 = new Thread(runImpl,"thread_11");
    Thread thread22 = new Thread(runImpl,"thread_22");
            Thread thread33 = new Thread(runImpl,"thread_33");


    thread11.start();
    thread22.start();
    thread33.start();
    }


    @Override
    public void run() {
    for (int i =0;i<50;i++){
    int next = this.getNext();
    System.out.println(Thread.currentThread().getName()+" runnable nextValue="+next);
    }
    }
    }

    启动三个线程一起执行一个value值得自增操作,能够出现偶尔的两个线程打印的值是相同的,也就说,这个value值按道理应该按照预想的每次执行getNext方法后返回的值都是自增过一次的,但是在多线程执行下,就可能出现不一样的结果。下面通过编译然后解析下class文件字节码:

       ...//省略之前部分
      public int getNext();
      descriptor: ()I
      flags: ACC_PUBLIC
      Code:
      stack=4, locals=1, args_size=1
      0: aload_0
      1: dup
      2: getfield #2 Field value:I
      5: dup_x1
      6: iconst_1
      7: iadd
      8: putfield #2 Field value:I
      11: ireturn
      LineNumberTable:
      line 12: 0


      public static void main(java.lang.String[]);
      ...

      上面的字节码就是截取getNext方法的,其他部分都省略了。可以看出来,这个value++自增的方法在字节码的情况下,是分成了先获取属性值 getfield、然后iadd自增、最后putfield基本三个步骤,这个三个步骤在cpu指令执行的时候是三个指令操作,也就说这期间多个线程同时操作这个方法时候,就有可能一个线程还在执行第二步自增的时候,另一个线程就走到第一步读取值,这时候读取的值就不是最新的值了。因为++操作在java里面不是原子操作的,字节码解析中就可以看出来。

      还有就是上面的value是实例变量,也就是线程可共享的资源,对于每个线程而言,都有自己对这个共享变量的一份副本,共享变量本身是放在主内存中的。每个线程操作共享变量值时候都是首先从主内存加载变量,然后读写这个变量值,最后将修改过的变量同步到主内存中。

      这也就可以解释出,上面value++操作时候的问题,一个线程操作了value值,还没有及时同步到主内存中,这个时候另一个线程就去copy一份主内存的值,然后读写操作,导致这个线程读取的值并不是最新的值,这就是所谓的线程可见性问题。

      针对这个解决可见性问题,一般可以使用synchronized关键字进行同步操作。synchronized关键字一般可以放在同步代码块(使用自定义对象加锁)、实例方法(默认就是当前对象)、静态方法(类的class对象)。

      备注:

      java文件使用命令编译:javac 文件名.java

      class文件查看字节码:javap -v 文件.class

      通过编译查看字节码文件,可以比较清楚代码的具体执行命令。

      文章转载自辉哥的菜,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

      评论