JAVA并发编程的挑战

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并发编程的艺术》

猜你喜欢

转载自blog.csdn.net/qq906627950/article/details/81187870