Java并发编程的艺术(一)并发编程的挑战

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/kukubao207/article/details/88660833

前言

今天是2019年03月19日,最近读了不少书,《Java并发编程的艺术》就是其中一本,这本书比较适合入门,讲的非常简单。这本书一共有11章,我已经看完第一遍了,有的章节看了两三遍,所以通过博客的形式梳理一番阅读过的内容。我不会把整本书的内容都放进来,我想写下来的是我理解了的部分,能够整理成块的内容。
并发编程就是用多线程的技术去达到更好的效率,但多线程必可避免会带来一些挑战,本文介绍了其中的两个挑战:

  • 上下文切换
  • 死锁

上下文切换

上下文切换的概念和影响

单核CPU的情况下,同一时间只能有一个线程占有CPU的时间片,那么线程在执行任务时,执行到一半就切换到另一个线程去了,下次回来这个线程的时候,任务还得继续执行,而跟这个任务相关的状态就需要重新被加载。上下文切换的开销在于,切换线程时前一个线程的状态的保存和切换到的线程的加载。

如何减少上下文切换的次数
  • 无锁编程,如CAS等
  • 减少线程数量
  • 使用协程,在单一线程中进行多任务的切换
Java减少上下文切换实战
  1. 用jps打出当前java虚拟机中的各种进程
    在这里插入图片描述
  2. 用jstack命令把某个进程的线程状态保存到一个文件中
    jstack 5313 > /root/jstack/machine_status_dump
    在这里插入图片描述
  3. 用grep结合相关命令统计线程状态
    grep java.lang.Thread.State machine_status_dump | awk '{print $2$3$4$5}'| sort | uniq -c
    在这里插入图片描述
  4. 如果WATING状态的线程数量过多,考虑减少线程池的核心线程数量。当然在本例中只有1+10个WATING的线程,相对而言还不算太多。

死锁

下面通过一道面试题的形式来阐述这个问题,有时候面试官会让手写一个死锁代码出来。
写代码其实考察的是背后对这个代码的思想的理解,我们首先得理解死锁是怎样产生的。在学习操作系统的时候,我们知道,死锁产生有四个条件。

  • 资源互斥:某个资源只能允许一个进程访问,这个进程在访问时,其他进程不可以访问
  • 占有且等待:占有一部分资源的进程,同时还缺一部分资源没拿到,在等待其他进程释放
  • 不可抢占:资源被一个进程占有时,不能被抢走
  • 循环等待:存在一个循环链,使得链中的所有进程都在等待前一个进程释放资源,形成了循环等待,大家都无法释放资源。

因此,在Java中,要写一个死锁代码,我们需要定义互斥(同一时刻只能被一个线程持有,这里通过Java的synchronize来实现)的资源A和B,要定义两个线程C和D,以synchronize的形式对资源A上锁,使得只有线程C能持有资源A,线程C持有A后,线程A的run方法里的代码还应该试图去持有资源B。而这时候得让资源B已经被线程D持有,同时线程D的run方法里也要试图去持有资源A,这样线程C和D就形成了循环等待
线程C持有A等待B,线程D持有B等待A,两者都不释放,构成死锁。
这就在这里插入图片描述在线程C获取到资源B之前,synchronize同步代码块不能往下执行了,也就无法释放掉线程C持有的资源A。下面看一下代码实现。

public class DeadLock {
    private static String A="A";
    private static String B="B";
    public static void main(String[] args){
        new Thread(()->{
            synchronized (A){
                System.out.println("线程C获取到资源A");
                try {
                    Thread.sleep(1000);  //本线程等1秒让线程D能够获取到资源B
                }catch (Exception e){
                    e.printStackTrace();
                }
                synchronized (B){
                    System.out.println("线程C获取到资源B");
                }
            }
        }).start();

        new Thread(()->{
            synchronized (B){
                System.out.println("线程D获取到资源B");
                try {
                    Thread.sleep(1000); //本线程等1秒让线程C能够获取到资源A
                }catch (Exception e){
                    e.printStackTrace();
                }
                synchronized (A){
                    System.out.println("线程D获取到资源A");
                }
            }
        }).start();
    }
}
执行结果:
线程C获取到资源A
线程D获取到资源B

猜你喜欢

转载自blog.csdn.net/kukubao207/article/details/88660833