Java并发编程的艺术
第一章
- 一个例子证明并行不一定快
package com.company;
public class ConcurrencyTest {
private static final long count = 100000;
public static void main(String[] args) throws InterruptedException {
concurrency();
serial();
}
//并行
private static void concurrency() throws InterruptedException {
long start = System.currentTimeMillis();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
int a = 0;
for (long i = 0; i < count; i++) {
a += 5;
}
}
});
thread.start();
int b = 0;
for (long i = 0; i < count; i++) {
b--;
}
thread.join();//调用线程,等执行完后再继续当前线程。这个保证thread也执行完了。
long time = System.currentTimeMillis() - start;
System.out.println("concurrency : " + time + "ms, b=" + b);
}
//串行
private static void serial() {
long start = System.currentTimeMillis();
int a = 0;
for (long i = 0; i < count; i++) {
a += 5;
}
int b = 0;
for (long i = 0; i < count; i++) {
b--;
}
long time = System.currentTimeMillis() - start;
System.out.println("serial: " + time + "ms, b=" + b);
}
}
/*输出:
concurrency : 2ms, b=-100000
serial: 3ms, b=-100000
*/
为甚么会这样呢,因为有上下文切换的事件
上下文切换:CPU从一个进程/线程切换到另一个进程/线程。任务从保存到再加载就是一次上下文切换。
- 工具:
- Lmbench3:用于测量上下文切换事件
- vmstat:测量上下文切换的次数(CS:Content Switch,上下文切换次数)
- 减少上下文切换:
- 无锁并发编程: 锁竞争会引起上下文切换,用一些方法避免使用锁(如将数据的ID按Hash算法取模分段,不同线程处理不同段的数据)
- CAS算法: CompareAndSwap算法。java.util.concurrent.*中的类使用CAS算法实现了区别于synchronized同步锁的一种乐观锁。JDK 5之前使用的是synchronized实现的独占锁(悲观锁)。
- 使用最小线程:避免不必要的线程。(如任务少就少创建几个)
- 使用协程:单线程中实现多任务的调度,并在单线程中维持多个任务间的切换。(Python使用yield可实现协程)
- 实战:
- 通过jstack这个JDK自带的堆栈分析工具进行分析。
//一个死锁
public static void deathLock() {
Thread t1 = new Thread() {
@Override
public void run() {
try {
lock1.lock();
TimeUnit.SECONDS.sleep(1);
lock2.lock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread t2 = new Thread() {
@Override
public void run() {
try {
lock2.lock();
TimeUnit.SECONDS.sleep(1);
lock1.lock();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
t1.setName("mythread1");
t2.setName("mythread2");
t1.start();
t2.start();
}
- 使用jps查看所有的Java 程序的 pid
15064
7672 RemoteMavenServer36
8552 Launcher
22476 ConcurrencyTest
5468 Jps
- jstack 22476 查看堆栈信息
打印了很多信息。
其中已经发现了死锁
Found one Java-level deadlock:
=============================
"mythread2":
waiting for ownable synchronizer 0x00000007809645d0, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
which is held by "mythread1"
"mythread1":
waiting for ownable synchronizer 0x0000000780964600, (a java.util.concurrent.locks.ReentrantLock$NonfairSync),
which is held by "mythread2"
-
死锁发生:
- t1拿到锁,因为异常没有释放
- t1拿到数据库锁,释放锁时出现异常,没释放。
-
死锁解决
- 避免一个线程同时获得多个锁
- 避免一个线程在锁内同时占用多个资源,尽量每个锁只占用一个
- 定时锁,lock.tryLock(timeout)代替内部锁机制
- 数据库锁,加锁和解锁放在一个数据库连接中。
-
资源限制的挑战
- 并发需要的资源量大于限制(软/硬件限制,如带宽,数据库连接数,socket连接数)。
- 使用集群并行执行程序
- 根据资源调整程序的并发度