并发编程的目的是为了让程序更快地运行,但是,启动更多的线程并不一点让程序最大程度的并行。
上下文切换
单核处理器也能多线程执行代码,处理器是通过时间片的快速切换来实现多线程的,时间片的时间非常短,所以CPU要不停的切换线程执行,时间片一般是十几毫秒。
所以当前任务执行完一个时间片后会切换到下一个任务。但是,在切换前会保存上一个线程的状态,下次切换回该线程时可以再加载这个任务的状态。任务从保存到加载就是一次上下文切换。
根据上下文切换的定义,CPU在切换线程时会保存状态和加载状态,这样的切换同样也是消耗时间的,频繁的上下文切换会影响程序的执行速度,以书中的程序为例:
public class ConcurrencyTest {
private static final long count = 10000l;
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--; }
long time = System.currentTimeMillis() - start; thread.join();
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+",a="+a);
}
当执行操作不超过百万次时,并行执行累加操作会比串行要慢。
通过vmstat测试上下文切换次数
程序每一秒切换1000多次
减少上下文切换可以通过减少线上的WAITING线程
死锁
简单的说有以下几种情况会造成死锁
1、忘记解锁
2、同一个线程中对同一个资源多次加锁
3、有两个线程,两个线程都不释放拥有的锁,也都获取不到等待的锁。
这样就会造成死锁,书上死锁的程序比较简单,这里就不多加描述了。
死锁对于程序来说一定是不可容忍的,我们一定要尽量避免死锁,以下几个方法可以一定程度上避免死锁
1、避免一个线程同时获得多个锁
2、避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
3、尝试使用定时锁
4、数据库锁的加锁和解锁必须在一个数据库连接里