1 public class VolatileThread implements Runnable {
2
3 private volatile int a = 0;
4
5 @Override
6 public void run() {
7 // synchronized (this){//②
8 a = a + 1;
9 // System.out.println(Thread.currentThread().getName()+"----"+a);
10 try {
11 Thread.sleep(100);
12 } catch (InterruptedException e) {
13 e.printStackTrace();
14 }
15 // System.out.println(Thread.currentThread().getName() + "####"+a);//①
16 a = a + 1;
17 System.out.println(Thread.currentThread().getName() + "****" + a);
18
19 // }
20 }
21 }
1 public class VolatileTest {
2 public static void main(String[] args) {
3 VolatileThread volatileThread = new VolatileThread();
4
5 for(int i=0;i<4;i++){
6 Thread t = new Thread(volatileThread);
7 t.setName("Thread-t"+i);
8 t.start();
9 }
10 }
11 }
以上代码,代码1是一个线程类,其中成员变量a被volatile修饰;代码2是main函数类,创建了4个线程。执行main函数,偶然发生了诡异结果现象:
Thread-t3****6
Thread-t0****7
Thread-t1****6
Thread-t2****6
疑问1:为什么会出现重复数字?疑问2:为什么7会现在比6先输出的现象?
先简单了解下volatile(下一篇将重点介绍volatile和Synchronized,本篇只是简单介绍下):
什么是volatile?volatitle起什么作用?
volatile是与java内存模型(JMM)有关的一个关键字,用来修饰成员变量,起作用:1、保证共享变量(成员变量)的可见性;2、指令有序性(禁止指令重排)。同Synchronized相比,它是轻量级,但不具有原子性,因此对于多线程来说,不是线程安全的。
如何理解可见性?
我们知道,每个线程都有自己的私有工作内存,在执行线程时,会从主内存拷贝一份成员变量到自己的工作内存,也就是变量副本,然后执行程序指令,最后把副本变量刷回主内存。对于A和B两个线程,同时要针对成员变量进行修改,比如a++,如果A线程执行完后还来及刷回主内存,B线程就开始拷贝原主内存的变量到自己的工作内存执行a++,此时,A线程所修改的变量对B线程而言是不可见的。而volatile的功能之一就是保证共享变量的可见性。
疑问1解析:
看看上面的代码,首先四个线程分别执行了第8行 a=a+1 后,分别进入sleep休眠,此时主内存的变量是4,线程分别休眠过期后,开始争夺cpu时间片,执行第16行的 a=a+1,理论上来说,上面的输出结果不会出现重复的变量数值,这又是为什么呢?原因要从a=a+1这个操作讲起,a=a+1,类似a++,这个操作并不是原子性操作,实际上是由从主内存中读取值、工作内存中+1操作、操作结果刷回主内存三步操作组成,在一条线程中,他们任意一步被打断,因此保证不了主内存中a一定+1,出现上面重复变量数值的结果。也就是我们常常说的volatile并不能保证线程安全。
疑问2解析:
有人会说,多线程在不加锁情况下,打印是随机的,原因是os处理器调度随机的。从这层面上理解是可以的,但作为程序员,为什么他是随机的,这期中有没有什么规则?我觉得务必要一探究竟。首先看下面println()的底层源码
public void println(String x) {
synchronized (this) {
print(x);
newLine();
}
}
看到Synchronized,不得不解释下,sync他本质上也是个非公平锁,也就是说多线程下,谁先拿到锁并不确定,当然谁拿到锁,谁就先打印。sync在Java1.6被称为一种重量级的锁,事实上,它底层的确涉及到的知识很多。还是耐心练功夫吧。