JAVA高并发编程--第四章读书笔记
的优化
减少锁的持有时间
对需要同步的代码部分使用同步代码块,对不需要同步的代码尽量不使用同步代码块。
较少锁的粒度
JDK的并发容器ConcurrentHashMap,如果对其整个进行加锁,加锁粒度太大,因为ConcurrentHashMap中细分了若干个HashMap,ConcurrentHashMap中可细分成16段,故可根据具体情况对某一段加锁减少加锁粒度。
用读写分离锁来代替独占锁
读写分离锁即ReadWriteLock,这是减少锁粒度的特殊情况。
锁分离
对读写锁的扩展,LinkedBlockingQueue是一个用链表实现的队列。在JDK中该类的put()和take()方法用两把不同的锁分离了。
锁粗化
频繁的释放和获取锁会消耗宝贵的资源。
虚拟机在碰到连续的对同一个锁进行请求和释放的操作时,会把所有的锁操作整合成对锁的一次请求,从而减少锁的请求次数,这就是锁粗化。
开发过程中也可以有意识的自行进行锁粗化的操作。
JAVA虚拟机对锁优化所做的努力。
锁偏向
锁偏向是一种针对加锁操作的优化手段。它的核心思想是:如果一个线程获得了锁,那么锁就进入偏向模式。当这个线程再次请求锁时,无须再做任何同步操作。这样就节省了大量有关锁申请的操作,从而提高了程序性能。因此,对于几乎没有锁竞争的场合,偏向锁有比较好的优化效果,因为连续多次极有可能是同一个线程请求相同的锁。而对于锁竞争比较激烈的场合,其效果不佳。因为在竞争激烈的场合,最有可能的情况是每次都是不同的线程来请求相同的锁。这样偏向模式会失效,因此还不如不启用偏向锁。使用Java虚拟机参数-XX:+UseBiasedLocking可以开启偏向锁。
轻量级锁
如果偏向锁失败,虚拟机并不会立即挂起线程。它还会使用- 种称为轻量级锁的优化手段。轻量级锁的操作也很轻便,它只是简单地将对象头部作为指针,指向持有锁的线程堆栈的内部,来判断一个线程是否持有对象锁。如果线程获得轻量级锁成功,则可以顺利进入临界区。如果轻量级锁加锁失败,则表示其他线程抢先争夺到了锁,那么当前线程的锁请求就会膨胀为重量级锁。
自旋锁
锁膨胀后,虚拟机为了避免线程真实地在操作系统层面挂起,虚拟机还会在做最后的努力一自旋锁。由于当前线程暂时无法获得锁,但是什么时候可以获得锁是一个未知数。 也许在几个CPU时钟周期后,就可以得到锁。如果这样,简单粗暴地挂起线程可能是一种得不偿失的操作。因此,系统会进行- - -次赌注:它会假设在不久的将来,线程可以得到这把锁。因此,虛报机会让当前线程做几个空循环(这也是自旋的含义),在经过若千次循环后,如果可以得到锁,那么就顺利进入临界区。如果还不能获得锁,才会真实地将线程在操作系统层面挂起。
锁消除
锁消除是一种更彻底的锁优化。Java 虚拟机在JIT编译时,通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁。通过锁消除,可以节省毫无意义的请求锁时间。
说到这里,细心的读者可能会产生疑问,如果不可能存在竞争,为什么程序员还要加上锁呢?这是因为在Java软件开发过程中,我们必然会使用一些JDK的内置API,比如StringBuffer、Vector等。你在使用这些类的时候,也许根本不会考虑这些对象到底内部是如何实现的。比如,你很有可能在一个不可能存在并发竞争的场合使用Vector。 而众所周知,Vector 内部使用了synchronized请求锁。比如下面的代码:
public string[ ]createstrings() {
vector<string> v=new Vector<string>();
For(int i=0;i<100;i++){
v.add (Integer.tostring(i));
}
return v.toArray (new String[ ]{ });
}
注意上述代码中的Vector,由于变量v只在createStrings()函数中使用,因此,它只是-一个单纯的局部变量。局部变量是在线程栈上分配的,属于线程私有的数据,因此不可能被其他线程访问。所以,在这种情况下,Vector内部所有加锁同步都是没有必要的。如果虚拟机检测到这种情况,就会将这些无用的锁操作去除。
锁消除涉及的一项关键技术为逃逸分析。所谓逃逸分析就是观察某一个变量是否会逃出某一个作用域。在本例中,变量v显然没有逃出createstrings()函 数之外。以次为基础,虚拟机才可以大胆地将v内部的加锁操作去除。如果createstrings()返回的不是String数组,而是v本身,那么就认为变量v逃逸出了当前函数,也就是说v有可能被其他线程访问。如果是这样,虚拟机就不能消除v中的锁操作。
逃逸分析必须在-server模式下进行,可以使用-XX:+DoEscapeAnalysis参数打开逃逸分析。使用-XX:+EliminateLocks参数可以打开锁消除。
ThreadLocal
是真的看8懂它写的鸡8
无锁
CAS算法:CAS(V,E,N),v(当前值)和e(期望值)相等时将v赋值给n(新值),v和e不相等时,说明已经有线程做了更新,当前线程就什么都不用做。
AtomicInteger是可变的且线程安全的,该类的方法使用了CAS操作,比使用锁有更好的性能。
补:java 中return;表示当前方式结束。Break表示当前循环结束,继续执行循环体下方的代码。Continue表示结束该次循环进入下次循环。
Unsafe
使用unsafe的getunsafe方法时,只有调用该方法的类的classloader为null时,他才会return theUnsafe;
AtomicReference和AtomicInteger类似,但是AtomicReference对应普通的对象引用,原书P190提供了一个范例指出AtomicReference有时候CAS操作无法正确判断当前数据状态。
AtomicStampedReference可解决上述问题,它内部维护了对象值和时间戳。
AtomicIntegerArray是对int【】类型的封装,使用Unsafe类通过CAS的方式控制int【】在多线程下的安全性。
利用 AtomicIntegerFieldUpdater 让普通变量享受原始操作,将普通变量封装进一个类,将该类传入AtomicIntegerFieldUpdater的静态方法newUpdater()中获取一个实例对象,该实例对象即可进行原子操作。