减小锁持有时间
对于使用锁进行并发控制的应用,锁竞争过程中,单个线程对锁的持有时间与系统性能有着直接的关系。下面代码中:
public synchronized void syncMethod() {
othercodel();
mutextMethod();
othercode2();
}
假设只有 mutextMethod() 需要同步,othercode1() 和 othercode2() 则会花费较长的 CPU时间。
优化的解决方案:
public void syncMethod2() {
othercode1();
synchronized(this) {
mutextMethod();
}
othercode2();
}
减小锁粒度
削弱多线程锁竞争的有效手段。典型的使用场景是 concurrentHashMap.
默认情况下,concurrentHashMap被进行进一步细分为16个段。
需要在 concurrentHashMap 中增加一个新的表项,并不是讲整个 HashMap 加锁,而是根据 hashcode 得到表项被存放在哪个段中,然后对该段加锁,并完成 put() 操作。读写分离锁来替换独占锁
使用 ReadWriteLock 可以提高系统的性能。锁分离
将读写锁的思想做进一步的延伸,就是锁分离。
典型的案例就是 java.util.concurrent.LinkedBlockingQueue 的实现。锁粗化
如果对同一个锁不停地进行请求、同步和释放,其本身也会消耗系统宝贵的资源,反而不利于性能的优化。
虚拟机在遇到一连串连续地对同一个锁不断进行请求和释放的操作时,便会把所有的锁操作整合成对锁的一次请求,从而减少对锁的请求同步次数,这个操作叫锁的粗化。java 虚拟机对锁优化所做的努力
6.1 锁偏向
核心思想是:如果一个线程获得了锁,那么锁就进入偏向模式。当这个线程再次请求锁时,无须
对于几乎没有锁竞争的场合,偏向锁有比较好的优化效果。对于所竞争比较激烈的场合,其效果不佳。
使用 java 虚拟机参数 -XX:+UseBiasedLocking 可以开启偏向锁。6.2 轻量锁
如果偏向锁失败,虚拟机并不会立即挂起线程。会使用一种称为轻量锁的优化手段。
如果线程获得轻量锁成功,则可以顺利进入临界区。
如果轻量锁加锁失败,则表示其他线程抢先争夺到了锁,那么当前线程的锁请求就会膨胀为重量级锁。6.3 自旋锁
锁膨胀后,虚拟机为了避免线程真实地在操作系统层面挂起,虚拟机还会做最后的努力,自旋锁。
虚拟机会让当前线程做几个空循环,在经过若干次循环后,如果可以得到锁,那么久顺利进入临界区。
如果还不能获得锁,才会真实地将线程在操作系统层面挂起6.4 锁消除
是一种更彻底的锁优化。java虚拟机在 JIT编译时,通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁。逃逸分析必须在 -server 模式下进行,可以使用 -XX:+DoEscapeAnalysis 参数打开。 使用 -XX:+EliminateLocks 参数可以打开锁消除
threadLocal
这是一个线程的局部变量。只有当前线程可以访问。
public class ThreadLocalDemo_Gc {
static volatile ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>() {
protected void finalize() throws Throwable {
System.out.println(this.toString() + " is gc");
}
};
static volatile CountDownLatch cd = new CountDownLatch(10000);
public static class ParseDate implements Runnable {
int i = 0;
public ParseDate(int i) {
this.i = i;
}
@Override
public void run() {
try {
if (tl.get() == null) {
tl.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss") {
protected void finalize() throws Throwable {
System.out.println(this.toString() + " is gc");
}
});
System.out.println(Thread.currentThread().getId() + ":create simpleDateFormat");
}
Date t = tl.get().parse("2017-01-03 12:12:" + i % 60);
} catch (ParseException e) {
e.printStackTrace();
} finally {
cd.countDown();
}
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService es = Executors.newFixedThreadPool(10);
for (int i = 0;i < 10000;i++) {
es.execute(new ParseDate(i));
}
cd.await();
System.out.println("mission complete");
tl = null;
System.gc();
System.out.println("first GC complete!!");
//在设置 ThreadLocal 的时候,会清楚 ThreadLocalMap 中的无效对象
tl = new ThreadLocal<SimpleDateFormat>();
cd = new CountDownLatch(10000);
for (int i = 0;i < 10000;i++) {
es.execute(new ParseDate(i));
}
cd.await();
Thread.sleep(1000);
System.gc();
System.out.println("second GC complete!!");
}
}
输出:
20:create simpleDateFormat
19:create simpleDateFormat
15:create simpleDateFormat
17:create simpleDateFormat
13:create simpleDateFormat
16:create simpleDateFormat
11:create simpleDateFormat
14:create simpleDateFormat
12:create simpleDateFormat
18:create simpleDateFormat
mission complete
first GC complete!!
com.xc.blackCap.thread.ThreadLocalDemo_Gc$1@635cb856 is gc
18:create simpleDateFormat
11:create simpleDateFormat
20:create simpleDateFormat
12:create simpleDateFormat
13:create simpleDateFormat
15:create simpleDateFormat
16:create simpleDateFormat
14:create simpleDateFormat
19:create simpleDateFormat
17:create simpleDateFormat
second GC complete!!
com.xc.blackCap.thread.ThreadLocalDemo_Gc$ParseDate$1@4f76f1a0 is gc
com.xc.blackCap.thread.ThreadLocalDemo_Gc$ParseDate$1@4f76f1a0 is gc
com.xc.blackCap.thread.ThreadLocalDemo_Gc$ParseDate$1@4f76f1a0 is gc
com.xc.blackCap.thread.ThreadLocalDemo_Gc$ParseDate$1@4f76f1a0 is gc
com.xc.blackCap.thread.ThreadLocalDemo_Gc$ParseDate$1@4f76f1a0 is gc
com.xc.blackCap.thread.ThreadLocalDemo_Gc$ParseDate$1@4f76f1a0 is gc
com.xc.blackCap.thread.ThreadLocalDemo_Gc$ParseDate$1@4f76f1a0 is gc
com.xc.blackCap.thread.ThreadLocalDemo_Gc$ParseDate$1@4f76f1a0 is gc
com.xc.blackCap.thread.ThreadLocalDemo_Gc$ParseDate$1@4f76f1a0 is gc
com.xc.blackCap.thread.ThreadLocalDemo_Gc$ParseDate$1@4f76f1a0 is gc
- 无锁的线程安全整数:AtomicInteger
为了让 Java 程序员能够受益于 CAS 等CPU指令,JDK并发包中有一个 atomic 包,
里面实现了一些直接使用 CAS 操作的线程安全的类型。