锁优化的思路和方法:
减少锁持有时间:
减少其他线程等待时间,只同步需要同步的相关的代码;
减少锁粒度:
将大对象拆成小对象,增加并行度,降低锁竞争;
偏向锁,轻量级锁成功率提高;
ConcurrentHashMap;
锁分离:
根据功能进行锁分离;
ReadWriteLock;
读多写少的情况,可提高性能;
读写分离思想延伸,只要操作互不影响,锁就可以分离;
LinkedBlockingQueue:
队列、链表;
从一端读,另一端写;
锁粗化:
通常情况,为保证多线程间有效并发,会要求每个线程持有锁的时间尽量短,即使用完后立即释放,
等待在这个锁上的其他线程才能今早获得资源;
但是,凡事有度,对一个锁不停的请求、同步和释放,其本身也会消耗系统宝贵资源,反而不利于性能的优化;
锁消除:
在即时编译器时,如果发现不可能被共享的对象,则可以消除其锁操作;
比如某些情况的StringBuffer等,比如局部变量的情况;
虚拟机内的锁优化方法:
基础:对象头MARK:
对象头的编辑,32位;
描述对象的hash、锁信息、垃圾回收标记、年龄;
指向锁记录指针、monitor指针、GC标记、偏向锁线程ID;
偏向锁:
大部分情况是没有竞争的,可通过偏向来提高性能;
所谓的偏向,就是偏心,即锁会偏向于当前已经占有锁的线程;
将对象头Mark的标记设置为偏向,并将线程ID写入对象头Mark;
只要没有竞争,获得偏向锁的线程在将来进入同步块,不需要做同步
当其他线程请求相同的锁时,偏向模式结束
-XX:+UseBiasedLocking,默认开启;
在竞争激烈的场合,偏向锁会增加系统负担;
轻量级锁:
*普通的锁处理性能不够理想,轻量级锁是一种快速的锁定方法;
*如果对象没被锁:将对象头的MARK指针保存到锁对象中;将对象设置为指向锁的指针(在线程栈空间中);
如果轻量级锁失败,表示存在竞争,升级为重量级锁(常规锁);
在没有锁竞争的前提下,减少传统锁使用OS互斥量产生的性能损耗;
在竞争激烈时,轻量级锁会做很多额外操作,导致性能下降;
自旋锁:
当竞争存在时,如果线程可以很快获得锁,那么可以不在OS层挂起线程,让线程做几个操作(自旋);
JDK1.6 中-XX:+UseSpinning开启;
JDK1.7中,去掉此参数,改为内置实现;
如果同步块很长,自旋失败,会降低系统性能;
如果同步块很短,自旋成功,节省线程挂起切换时间,提升系统性能;
总结:
这几种锁不是java语音层面的锁优化方法,jvm虚拟机层面的;
内置于JVM中获取锁的优化方法,和获取锁的步骤:
-偏向锁可用会先尝试偏向锁;轻量级锁可用会先尝试轻量级锁;
-以上都失败,尝试自旋锁;再失败则尝试普通锁,使用OS互斥量在在操作系统层挂起;
一个错误使用锁的案例:
看起来是没有问题的,Integer是个不变的对象,不懂得线程把不同的i的引用赋给了i;
public class IntegerLock {
static Integer i=0;
public static class AddThread extends Thread{
public void run(){
for(int k=0;k<100000;k++){
synchronized(i){
i++;
}
}
}
}
public static void main(String[] args) throws InterruptedException {
AddThread t1=new AddThread();
AddThread t2=new AddThread();
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
}
ThreadLocal及其源码分析:
彻底把锁去掉;
为每一个线程提供一个对象实例;维护一个线程的局部变量;
源码分析:每个线程都维护一份自己的ThreadLocal.ThreadLocalMap,从线程Thread中获取的一个成员变量threadLocals;
key即是ThreadLocal本身,value是设置的值;
比如:SimpleDateFormat是非线程安全的,被多个线程操作时可能产生错误;
static ThreadLocal<SimpleDateFormat> tl=new ThreadLocal<SimpleDateFormat>();
public static class ParseDate implements Runnable{
int i=0;
public ParseDate(int i){this.i=i;}
public void run() {
try {
if(tl.get()==null){
tl.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}
Date t=tl.get().parse("2015-03-29 19:29:"+i%60);
System.out.println(i+":"+t);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(10);
for(int i=0;i<1000;i++){
es.execute(new ParseDate(i));
}
}