摘抄自周志明老师的《深入理解Java虚拟机:JVM高级特性与最佳实践》13.2.2 线程安全的实现方法
1.名词解释
同步是指锁哥线程并发访问共享数据时,保证共享数据同一时刻只被一个线程访问
互斥同步(阻塞同步)是一种悲观的并发策略,总是认为只要不去做正确的同步措施(加锁),那就肯定会出现问题。
阻塞同步最主要的问题是进行线程阻塞和唤醒所带来的性能问题,因为在JDK 1.2之后,Java的线程模型被替换为基于操作系统原生线程模型来实现,如要阻塞唤醒一个线程,都需要操作系统来帮忙完成,这就需要用户态转换到内核态,会耗费很多时间。
非阻塞同步:随着硬件指令集的发展,我们有了另一种选择:基于冲突检测的乐观并发策略,这种操作不需要挂起线程,会节省很多时间,比如CAS(compare and swap)指令。
2.开始测试
第一种加锁方式:
static int a=0; public static void lock() { Thread[] threads=new Thread[50]; for(int i=0;i<threads.length;i++){ threads[i]=new Thread(new Runnable() { @Override public void run() { for(int i=0;i<100000;i++){ synchronized(Main.class){ a++; } } } }); threads[i].start(); } while(Thread.activeCount() > 1){ Thread.yield(); } System.out.println(a); }
第二种CAS方式:
static AtomicInteger integer=new AtomicInteger(0); public static void cas() { Thread[] threads=new Thread[50]; for(int i=0;i<threads.length;i++){ threads[i]=new Thread(new Runnable() { @Override public void run() { for(int i=0;i<100000;i++){ integer.incrementAndGet(); } } }); threads[i].start(); } while(Thread.activeCount() > 1){ Thread.yield(); } System.out.println(integer.get()); }
3结论:运行两个方法,同样是50个线程,100000个自增,输出时间差,可以看到性能之间的差异。
lock方法平均在1270ms
cas方法平均在175ms
可以看到阻塞同步与非阻塞同步之间巨大的差异,也可以看出Java线程用户态内核态切换损耗的处理器时间很大。