定义:
当多个线程访问某个类时,不管运行时环境采用何种调度方式 或者这些线程如何交替执行,并且在主调代码中不需要任何额外的同步或者协同,这个类都能表现正确的行为,那么称这个类时线程安全的。
线程安全性主要体现在三个方面:
(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();}@Overridepublic 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: ()Iflags: ACC_PUBLICCode:stack=4, locals=1, args_size=10: aload_01: dup2: getfield #2 Field value:I5: dup_x16: iconst_17: iadd8: putfield #2 Field value:I11: ireturnLineNumberTable:line 12: 0public 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
通过编译查看字节码文件,可以比较清楚代码的具体执行命令。




