synchronized
进入、退出同步块时会刷新所有工作内存,使得其他线程对刚退出同步块的线程对共享变量所作的更新可见。通过锁机制,synchronized实现了原子性和有序性,即同一时刻只能有一个线程进入临界区,且保证了下一个线程进入临界区之前上一个线程已经退出临界区。
volatile
同样通过刷新工作内存,实现了可见性,通过禁止指令重排序实现了有序性(个人理解:首先禁止编译器对jvm指令编译成机器指令时的重排序,其次禁止cpu执行机器指令时的指令重排序)。
1 public class TestSynchronizedAndVolatile { 2 private boolean b = false; 3 private volatile Object lock = new Object(); 4 5 /** 6 * 普通变量的写操作,不但会写入缓存,还会写回主内存 7 */ 8 @Test 9 public void test1() throws InterruptedException { 10 new Thread(new Runnable() { 11 @Override 12 public void run() { 13 try { 14 /** 15 * 为了判断主线程中b=true是写入了主内存,还是仅写入工作内存。 16 * 之后的循环会跳出,说明了主线程中b=true回写到了主内存中。 17 */ 18 Thread.sleep(100); 19 } catch (InterruptedException e) { 20 } 21 while (true) { 22 if (b) { 23 break; 24 } 25 b = false; 26 } 27 System.out.println("end"); 28 } 29 }).start(); 30 b = true; 31 Thread.sleep(1000); 32 } 33 /** 34 * 线程中第一次读取到共享变量后,该变量会缓存在工作内存中, 35 * 再次访问时直接从工作内存中读取,而不会重新从主内存中读取。 36 * 这就造成了多线程访问共享变量时,访问到的是旧值。 37 */ 38 @Test 39 public void test2() throws InterruptedException { 40 new Thread(new Runnable() { 41 @Override 42 public void run() { 43 while (true) { 44 /** 45 * 主线程中b=true后于子线程执行,虽然b=true已经作用于主内存, 46 * 但此前子线程已经将b读入工作内存(缓存),子线程没有动机再次从主线程同步b, 47 * 故这里会无限循环 48 */ 49 if (b) { 50 break; 51 } 52 } 53 System.out.println("end"); 54 } 55 }).start(); 56 Thread.sleep(100); 57 b = true; 58 Thread.sleep(100); 59 } 60 61 /** 62 * synchronized会刷新缓存 63 */ 64 @Test 65 public void test3() throws InterruptedException { 66 new Thread(new Runnable() { 67 @Override 68 public void run() { 69 Object lock = new Object(); 70 while (true) { 71 /** 72 * 加入synchronized同步后情况发生了改变,当b=true同步回主内存后, 73 * 子线程中的b与主内存的值相同,循环跳出。 74 * 可见进入synchronized时会刷新工作内存(所有线程的工作内存),从而使得读取b时必须从主内存读取。 75 */ 76 synchronized (lock) { 77 if (b) { 78 break; 79 } 80 } 81 } 82 System.out.println("end"); 83 } 84 }).start(); 85 Thread.sleep(100); 86 b = true; 87 Thread.sleep(100); 88 } 89 90 /** 91 * volatile会刷新缓存 92 * @throws InterruptedException 93 */ 94 @Test 95 public void test4() throws InterruptedException { 96 new Thread(new Runnable() { 97 @Override 98 public void run() { 99 Object obj = null; 100 while (true) { 101 /** 102 * 加入synchronized同步后情况发生了改变,当b=true同步回主内存后, 103 * 子线程中的b与主内存的值相同,循环跳出。 104 * 可见进入synchronized时会刷新工作内存(所有线程的工作内存),从而使得读取b时必须从主内存读取。 105 */ 106 if (b) { 107 break; 108 } 109 System.out.println(b); 110 /** 111 * 由于lock是一个volatile,为了读到最新的lock值,这里就会刷新工作内存, 112 * 这样的话,当再次进入循环时,b就需要从主内存读取,所以可以跳出循环。 113 * (这里只体现了volatile可见性的语义,并没有体现禁止指令重排序的语义, 114 * 个人对指令重排序的理解分为两层,一层是编译器将jvm指令转换为机器指令时的重排序, 115 * 另一层是多核cpu并行执行指令造成的指令执行顺序的乱序。 116 * 而可见性则可以通过刷新所有线程的工作内存实现。) 117 */ 118 obj = lock; 119 System.out.println("lock"); 120 } 121 System.out.println("end"); 122 } 123 }).start(); 124 Thread.sleep(10); 125 b = true; 126 Thread.sleep(100); 127 } 128 }