jvm第7节-锁(偏向锁,轻量锁,自旋锁)
在介绍锁之前我们先介绍一个线程不安全的例子,一个全局的list,开2个线程往里面插入数据,代码如下:
package com.jvm.day6.lock.demo; import java.util.ArrayList; import java.util.List; /** * 测试都线程共享一个变量带来的现象 * @Author:xuehan * @Date:2016年3月20日下午3:35:29 */ class NumberAdd implements Runnable{ public static List<Integer> numberList =new ArrayList<Integer>(); public static int startNum; public NumberAdd(int startNum){ this.startNum = startNum; } @Override public void run() { int count = 0; while(count < 1000000){ numberList.add(count ++ ); startNum = startNum + 2; } } } public class Test{ public static void main(String[] args) throws Exception { Thread t1 = new Thread(new NumberAdd(1)); Thread t2 = new Thread(new NumberAdd(0)); t1.start(); t2.start(); while(t1.isAlive() || t2.isAlive()){Thread.sleep(2);} System.out.println("集合大小" + NumberAdd.numberList.size() ); System.out.println( "最后一个值的大" + NumberAdd.numberList.get(NumberAdd.numberList.size() - 1)); for(int i = NumberAdd.numberList.size() - 10 ; i < NumberAdd.numberList.size() -1; i ++){ System.out.println(NumberAdd.numberList.get(i)); } } }
按照开始想的,集合里面应该有200万个数据了,结果却出现了数组越界的错误,为什么呢,这是因为ArrayList不是线程安全的,用来存放数据的elementData是共享的, 线程A往list里添加数据的时候刚验证大小通过,还没有插入,然后轮到线程B执行,线程B刚好插入了list该扩容的最后一个元素,然后list满了,线程A执行,A线程往集合里面插入元素,引起了数据越界。
jvm锁
每个对象都一个mark头,他的作用是:
Mark Word,对象头的标记,32位
描述对象的hash、锁信息,垃圾回收标记,年龄
指向锁记录的指针
指向monitor的指针
GC标记
偏向锁线程ID
package com.jvm.day6.lock; import java.util.List; import java.util.Vector; /** *使用 偏向锁-XX:+UseBiasedLocking -XX:BiasedLockingStartupDelay=0 * 不使用偏向锁-XX:-UseBiasedLocking * 根据测试下面代码使用偏向锁可提高150毫秒执行时间 150/2300,提高效率6% * @Author:xuehan * @Date:2016年3月19日下午12:06:15 */ public class DeflectionLock { public static List<Integer> numberList =new Vector<Integer>(); public static void main(String[] args) throws InterruptedException { System.out.println((int)'l'); long begin=System.currentTimeMillis(); int count=0; int startnum=0; while(count<10000000){ numberList.add(startnum); startnum+=2; count++; } long end=System.currentTimeMillis(); System.out.println(end-begin); } }根据我的写实偏向锁可以提高性能6%
如果轻量级锁失败,表示存在竞争,升级为重量级锁(常规锁)
在没有锁竞争的前提下,减少传统锁使用OS互斥量产生的性能损耗
在竞争激烈时,轻量级锁会多做很多额外操作,导致性能下降
上面的一些锁不是Java语言层面的锁优化方法
他们是内置于JVM中的获取锁的优化方法和获取锁的步骤
偏向锁可用会先尝试偏向锁
轻量级锁可用会先尝试轻量级锁
以上都失败,尝试自旋锁
再失败,尝试普通锁,使用OS互斥量在操作系统层挂起
我们可以从以下方面对锁进行优化
减少锁的时间
没必须放在同步块的代码尽量不要放在代码块里
减少锁的粒度
将大对象,拆成小对象,大大增加并行度,降低锁竞争
偏向锁,轻量级锁成功率提高
实现的例子如ConcurrentHashMap
使用若干个Segment :Segment<K,V>[] segments
Segment中维护HashEntry<K,V>
put操作时
先定位到Segment,锁定一个Segment,执行put
在减小锁粒度后, ConcurrentHashMap允许若干个线程同时进入
锁分离
根据功能进行锁分离
ReadWriteLock
读多写少的情况,可以提高性能
读写分离思想可以延伸,只要操作互不影响,锁就可以分离
LinkedBlockingQueue
队列
链表
锁粗化
通常情况下,为了保证多线程间的有效并发,会要求每个线程持有锁的时间尽量短,即在使用完公共资源后,应该立即释放锁。只有这样,等待在这个锁上的其他线程才能尽早的获得资源执行任务。但是,凡事都有一个度,如果对同一个锁不停的进行请求、同步和释放,其本身也会消耗系统宝贵的资源,反而不利于性能的优化
for(int i=0;i<1000;i++){ synchronized(lock){} } // 应该写成 synchronized(lock){ for(int i =0; i < 1000; i ++){} }
这时候我们要增加锁的持有时间不要让请求和释放锁频繁的发生
锁消除
在java方法体里如果不是共享的变量不需要同步操作的,这时候jvm会自动的优化把锁去掉,如StingBuffer和Vector,使用锁消除
-server -XX:+DoEscapeAnalysis -XX:+EliminateLocks
关闭锁消除
-server -XX:+DoEscapeAnalysis -XX:-EliminateLocks
无锁
锁是悲观的操作
无锁是乐观的操作
无锁的一种实现方式
CAS(Compare And Swap),CAS是原子的
非阻塞的同步
CAS(V,E,N)
在应用层面判断多线程的干扰,如果有干扰,则通知线程重试,一般这样做会让程序变的复杂,但性能更加好。