1、上下文切换
-
单核CPU是如何实现多个线程同时实现的?
-
CPU通过时间片分配算法来循环执行任务,如系统中有三个线程,那么对于CPU来说就是不断的在这三个线程中循环切换执行。
-
因为CPU的运行速度相当快,所以我们人类感觉不到这个切换的过程,所以会任务这几个线程是同时执行的。
-
线程切换的时候,CPU会保存当前线程的任务状态,以便于下次切换回这个线程时可以加载这个状态并继续往下执行。
-
任务从保存到再加载的过程就是一次上下文切换。
-
我们可以看出,每次切换都会造成一些不必要的开销,所以当线程的数量越多的时候,这个开销将会越大。
-
-
如何减少上下文的切换?
-
无锁并发编程:多个线程在竞争锁的时候会引发上下文的切换,所以在编码的过程中,我们最好能够做到尽量少的使用锁机制。如ConcurrentHashMap,该类通过使用数据分段的形式来减少锁的竞争,当不同线程读写不同的数据段时,此时的操作就可以不用加锁。
-
CAS算法:可以通过乐观锁的方式来保障线程的安全性,如JAVA中Atomic包中的类。
-
使用最少的线程:避免创建不需要的线程。
-
协程:在单线程里实现多任务的调度,并在单线程里维持多个任务间的切换。
-
2、死锁
-
常见情景:
-
1)同步的嵌套。
-
2)线程1和线程2都出于wait状态,线程1等待线程2来notify,线程2等待线程1来notify
-
-
死锁代码
private Object obj1 = new Object(); private Object obj2 = new Object(); public void a() { synchronized (obj1) { synchronized (obj2) { System.out.println("a"); } } } public void b() { synchronized (obj2) { synchronized (obj1) { System.out.println("b"); } } }
-
避免死锁的常见方法:
-
避免一个线程同时获取多个锁。
-
避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
-
尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制。
-
对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。
-
3、资源限制
-
什么是资源的限制?
-
如服务器的带宽是2Mb/s,某个数据的下载速度是1Mb/s,就算启用100个线程,下载速度也不会变为100Mb/s。
-
所以在并发编程时需要考虑这些限制,如带宽、磁盘IO及CPU处理速度等。软件资源限制有数据库的连接数、socket连接数等。
-
-
在资源限制情况下如何进行并发编程?
-
根据不同的资源限制调用程序的并发度。
-
如上面所说到的数据下载,由于带宽的限制是2Mb/s,那么此时我们将并发下载的线程数设置为2是最合理的,因为若线程数量过多,此时由于上下文切换及资源调度的缘故,反而会导致下载更慢。
-
-
如何解决资源限制问题?
-
对于硬件资源限制,可考虑使用集群并行执行程序。
-
对于软件资源限制,可以考虑使用资源池将资源复用。
-
本文参考自方腾飞《JAVA并发编程的艺术》