java中与线程安全有关的关键字:volatile、synchronized
volatile Java中Volatile关键字详解
可见性 volatile修饰的变量不允许线程内部缓存(保证可见性),即直接修改内存,所以对其他线程是可见的。
变量经过 volatile 修饰后,对此变量进行写操作时,汇编指令中会有一个LOCK前缀指令,这个不需要过多了解,但是加了这个指令后,会引发两件事情:
将当前处理器缓存行的数据写回到系统内存。
这个写回内存的操作会使其他处理器缓存数据的内存地址无效。
即
对于写操作:对变量更改完之后,要立刻写回到主存中。
对于读操作:对变量读取的时候,要从主存中读,而不是缓存。
问题来了,既然它可以保证修改的值立即能更新到主存,其他线程也会捕捉到被修改后的值,那么为什么不能保证原子性呢?
比如 volatile int a = 0;
之后有一个操作 a++;
这个变量a具有可见性,但是 a++
依然是一个非原子操作,也就是这个操作同样存在线程安全问题。
Java中只有对基本类型变量的赋值和读取是原子操作,如 i = 1;
的赋值操作,但是像 j = i;
或者 i++;
这样的操作都不是原子操作,存在线程安全问题。比如先读取 i
的值,再将 i
的值赋值给 j
,两个原子操作加起来就不是原子操作了。
所以,一个变量被volatile修饰,保证了每次读取的变量值是最新的。像变量自增这样的非原子操作,这个变量就不具有原子性了。
有序性 volatile 规定 禁止指令重排序
,为了保证数据的一致性。
可见性示例 示例1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 public class Test { private static boolean flag = true ; public static void main (String[] args) throws InterruptedException { new Thread (() -> { System.out.println("ThreadA开始执行..." ); while (flag) { if (!flag) { System.out.println("跳出循环" ); break ; } } System.out.println("ThreadA结束执行。。。" ); }, "ThreadA" ).start(); TimeUnit.SECONDS.sleep(1 ); flag = false ; System.out.println("标识已经变更" ); } }
输出:
示例2
示例1变形,改变 while 循环体内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public class Test { private static boolean flag = true ; public static void main (String[] args) throws InterruptedException { new Thread (() -> { System.out.println("ThreadA开始执行..." ); while (flag) { System.out.println("跳出循环" ); } System.out.println("ThreadA结束执行。。。" ); }, "ThreadA" ).start(); TimeUnit.SECONDS.sleep(1 ); flag = false ; System.out.println("标识已经变更" ); } }
输出:
1 2 3 4 5 6 ThreadA开始执行... 跳出循环 ... 跳出循环 标识已经变更 ThreadA结束执行。。。
示例3
继续改变 while 循环体内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Test { private static boolean flag = true ; public static void main (String[] args) throws InterruptedException { new Thread (() -> { System.out.println("ThreadA开始执行..." ); int i = 0 ; while (flag) { i++; } System.out.println("ThreadA结束执行。。。" ); }, "ThreadA" ).start(); TimeUnit.SECONDS.sleep(1 ); flag = false ; System.out.println("标识已经变更" ); } }
输出:
总结
有以上 3 个示例可得出,flag 的可见性会根据 while 循环体内容儿变化,不知道为什么,请指教。
所以为了保证 flag 可见,用 volatile 修饰。
非原子性示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 public class Test { private static volatile int num = 0 ; public static void main (String[] args) throws InterruptedException { for (int j = 0 ; j < 2 ; j++) { new Thread (() -> { for (int i = 0 ; i < 10000 ; i++) { Test.num++; } }, String.valueOf(j)).start(); } while (Thread.activeCount() > 2 ) { Thread.yield (); } System.out.println(Thread.currentThread().getName() + "final num result = " + Test.num); Thread.sleep(5000 ); System.out.println(Thread.currentThread().getName() + "final num result = " + Test.num); } }
输出:
1 2 3 main final num result = 17114 main final num result = 20000 main final num result = 19317
三次执行,都是不同的结果。
为什么会出现这种呢?因为 num++
被拆分成3个指令:
执行 getfield 拿到主内存中的原始值 num。
执行 iadd 进行加1操作。
执行 putfield 把工作内存中的值写回主内存中。
当多个线程并发执行 putfield 指令的时候,会出现写回主内存覆盖问题,所以才会导致最终结果不为 20000,所以 volatile 不能保证原子性。
总结
volatile可用于内存访问的可见性,保证每次读的都是最新写的数据
volatile不具备原子性,原子功能需要使用Atomic类
原子操作和原子操作合并之后,不是原子操作。
synchronized 什么是同步锁 sychronized 采取的同步策略 是互斥同步 。
在每次获取资源之前,都需要检查是否有线程占用该资源。已经进入的线程尚未执行完,将会阻塞后面其他线程。
同步锁的本质是对象实例 。多线程安全需保证每个线程使用的是同一个锁对象 。
同步锁保证多线程下的每个操作都是原子性的。
同步锁、同步监听对象、同步监视器、互斥锁 它们都是同步锁,说的是一个意思。
弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
1、对象是什么?
我们可以随便创建一个对象试试。
2、需要同步的代码是哪些?
把多条语句操作共享数据的代码 部分给包起来
注意:
使用方式 同步代码块(对象锁、类锁) 格式:
1 2 3 4 5 6 7 8 9 10 11 synchronized (对象) { } synchronized (this ) { } synchronized (类.class) { }
优化 SellTicket :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 public class SellTicketDemo { public static void main (String[] args) { SellTicket st = new SellTicket (); Thread t1 = new Thread (st, "窗口1" ); Thread t2 = new Thread (st, "窗口2" ); Thread t3 = new Thread (st, "窗口3" ); t1.start(); t2.start(); t3.start(); } } class SellTicket extends Stock implements Runnable { private Object objLock = new Object (); @Override public void run () { StopWatch stopWatch = new StopWatch (); stopWatch.start(); while (sellTickets < originalStock) { synchronized (objLock) { if (sellTickets < originalStock) { try { Thread.sleep(100 ); System.out.println(Thread.currentThread().getName() + "\t\t正在出售第" + (++sellTickets) + "张票\t\t" + "原始库存" + originalStock + "张票\t\t" ); } catch (InterruptedException e) { e.printStackTrace(); } } } } stopWatch.stop(); System.out.println(Thread.currentThread().getName() + "总耗时: " + stopWatch.getTotalTimeNanos()); } }
同步方法(对象锁) 把同步加在方法上。
这里的锁对象是this
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 public class SellTicketDemo { public static void main (String[] args) { SellTicket st = new SellTicket (); Thread t1 = new Thread (st, "窗口1" ); Thread t2 = new Thread (st, "窗口2" ); Thread t3 = new Thread (st, "窗口3" ); t1.start(); t2.start(); t3.start(); } } class SellTicket extends Stock implements Runnable { @Override public void run () { StopWatch stopWatch = new StopWatch (); stopWatch.start(); while (sellTickets < originalStock) { sell(); } stopWatch.stop(); System.out.println(Thread.currentThread().getName() + "总耗时: " + stopWatch.getTotalTimeNanos()); } private synchronized void sell () { if (sellTickets < originalStock) { try { Thread.sleep(100 ); System.out.println(Thread.currentThread().getName() + "\t\t正在出售第" + (++sellTickets) + "张票\t\t" + "原始库存" + originalStock + "张票\t\t" ); } catch (InterruptedException e) { e.printStackTrace(); } } } private void sell () { synchronized (this ) { if (sellTickets < originalStock) { try { Thread.sleep(100 ); System.out.println(Thread.currentThread().getName() + "\t\t正在出售第" + (++sellTickets) + "张票\t" + "\t" + "原始库存" + originalStock + "张票\t\t" ); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
静态同步方法(类锁) 把同步加在方法上。只需要在同步方法上加一个 static 。
这里的锁对象是当前类的Class对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 public class SellTicketDemo { public static void main (String[] args) { SellTicket st1 = new SellTicket (); SellTicket st2 = new SellTicket (); SellTicket st3 = new SellTicket (); Thread t1 = new Thread (st1, "窗口1" ); Thread t2 = new Thread (st2, "窗口2" ); Thread t3 = new Thread (st3, "窗口3" ); t1.start(); t2.start(); t3.start(); } } class SellTicket extends Stock implements Runnable { @Override public void run () { StopWatch stopWatch = new StopWatch (); stopWatch.start(); while (sellTickets < originalStock) { sell(); } stopWatch.stop(); System.out.println(Thread.currentThread().getName() + "总耗时: " + stopWatch.getTotalTimeNanos()); } private static synchronized void sell () { if (sellTickets < originalStock) { try { Thread.sleep(100 ); System.out.println(Thread.currentThread().getName() + "\t\t正在出售第" + (++sellTickets) + "张票\t\t" + "原始库存" + originalStock + "张票\t\t" ); } catch (InterruptedException e) { e.printStackTrace(); } } } private void sell () { synchronized (this .getClass()) { if (sellTickets < originalStock) { try { Thread.sleep(100 ); System.out.println(Thread.currentThread().getName() + "\t\t正在出售第" + (++sellTickets) + "张票\t" + "\t" + "原始库存" + originalStock + "张票\t\t" ); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
Double-Check单例模式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Singleton { private volatile static Singleton singleton; private Singleton () {} public static Singleton getSingleton () { if (singleton == null ) { synchronized (Singleton.class) { if (singleton == null ) { singleton = new Singleton (); } } } return singleton; } }