java高并发程序设计学习笔记九锁的优化和注意事项

锁优化的思路和方法:

减少锁持有时间:

减少其他线程等待时间,只同步需要同步的相关的代码;

减少锁粒度:

将大对象拆成小对象,增加并行度,降低锁竞争;

    偏向锁,轻量级锁成功率提高;

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));
}
}




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章