目录

Life in Flow

知不知,尚矣;不知知,病矣。
不知不知,殆矣。

X

进程、线程、多线程

操作系统、进程、线程之间的关系

操作系统、进程、线程之间的关系

 操作系统是包含多个进程的容器,而每个进程又都是容纳多个线程的容器。

  • 进程:使用 fork(2) 系统调用创建的 UNIX 环境(比如:文件描述符,用户 ID 等),它被设置为运行程序。 进程就是代码的实例化对象。是资源分配的基本单位。
  • 线程:在进程上下文中执行的一系列指令。

Windows 下查看进程下所有线程

PsList v1.4

* 然后将解压后的 pslist.exe 复制到 C:\Windows\System32 文件夹下
* 执行  PSlist  -dmx  PID   (用来查看指定进程下有哪些线程)
* windows10 可以启动  资源监视器 来查看 java.exe进程的线程数

模拟线程

/**
 * 创建 100 个线程,用任务管理器的CPU栏目看Java线程数量的变化,10秒钟后线消失。
 */
public class Create100Threads {
    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(10000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }
}

进程和线程的不同

  • 起源不同:处理器速度比外设要快(键盘、硬盘),为了提高 CPU 利用率才诞生了线程,以提高程序的执行效率。
  • 概念不同:进程是具有独立功能的程序运行单元,是系统分配资源和调度基本单位。线程是 CPU 的调度单位。
  • 内存共享方式:不同进程之间的内存通常情况下是不共享的(除非使用 IPC 进程间通讯), 线程与线程之间内存和通信相比进程要容易的多,因为它们本身服务于同一个进程。
  • 拥有资源不同
# 线程共享的内容
* 进程代码段
* 进程的公有数据(利用这些共享的数据,线程很容易的实现相互的通讯)
* 进程打开的文件描述符
* 信号处理器
* 进程的当前目录
* 进程用户 ID 与 进程组 ID

# 线程独有的内容
* 线程ID
* 寄存器组的值
* 线程的堆栈
* 错误返回码
* 线程的信号屏蔽码
  • 数量不同:1 个进程包含多个线程,1 个线程至少拥有一个线程
  • 开销不同
* 线程的创建、终止时间比进程短
* 同一进程内的线程切换时间比进程切换短
* 同一进程的各个线程间共享内存和文件资源,可以不通过内核进行通信

进程和线程的相似点

  • 生命周期:状态上很相似。

Java 语言和多线程的渊源

  • Java 设计之初支持多线程
  • Java 语言多用于服务端(服务端经常需要用到多线程)
  • 一对一映射到操作系统的内核线程

JVM 自动启动线程

  • Signal Dispatcher:把操作系统发来的信号分发给适当的处理程序。(连接操作系统和应用程序)。
  • Finalizer:每个对象结束的时候会执行对象的 finalize() 方法。虽然 finalize() 已经不再推荐使用。
  • Reference Handler:和 GC、引用相关的线程。
  • main:主线程,用户程序的入口。

多线程

多线程程序:如果一个程序允许运行两个或以上的线程,那么它就是多线程程序。
多线程:多线程是指在单个进程中运行多个线程。

为什么需要多线程?

阿姆达尔定律

  • 最主要的目的就是提高 CPU 利用率:多线程程序可以充分利用多核 CPU 资源。(除 CPU 之外的其他外设速度太慢)
  • 避免无效等待(IO 的时候可以做别的事情)
  • 提高用户体验:避免卡顿、缩短等待时机
  • 便于编程建模(将一个复杂的任务分解成若干的子任务)
  • 摩尔定律失效 (纳米级别到达了极限)
  • 阿姆达尔定律:处理器越多,程序执行就越快,但有上限,取决于程序中串行部分的比例和并行的比例,程序的并行比例越高,多处理器的效果越明显。(如果程序本身的代码全部都是串行的,那么多线程并不能提高运行速度)
* tomcat 用多个线程接收进来的HTTP请求,而不是排队等待单一的线程处理
* Android 主线程的重要任务之一就绘制屏幕节界面,该主线程中不允许进行IO/操作或网络请求,目的就是为了避免卡顿,影响用户的交互

什么场景中会用到多线程

  • 为了同时做多件不同的事(打开网页同时听音乐、后台定时任务)
  • 为了提高工作效率、处理能(Tomcat、并行下载、NIO)
  • 需要同时有很大并发量的时候(压测模拟请求)

多线程的局限性

  • 性能问题:上下文切换带来的消耗。
  • 异构化任务(任务结构不一样)很难高效并行
  • 带来线程安全问题:包括数据安全问题(例如 i++ 总数的不一致)以及线程带来的活跃性问题(线程饥饿、死锁等…)

串行、并行(Parallelism)、并发(Concurrent)

串行、并行、并发

  • 串行:排队
  • 并发(Concurrent):单处理器的逻辑上同时执行。在同一时刻只能运行一个线程,多个线程交替运行在同一。
  • 并行(Parallelism):多处理器的物理上的同时执行。在同一时间多个处理器同时执行多个任务,并发不一定是并行(如果是单处理器)。

并发的 2 种概念

  • 形容多个任务的执行状态(多个任务看上去表面上可以同时执行):并行一定是并发。(并发可以通过添加处理器数量升级为并行)。
  • 对 “并发性” 的简称
# 具有并发性的程序
* 不同的部分可以无序或同时执行,且不影响最终的执行结果
* 在不同核心数的计算机上的不同表现
* 并行和并发的概念并不在同一纬度
* 程序具有并发性是并发执行或并行执行的前提条件和必要条件。

单核 CPU 运行多线程的意义?

  • 单核 CPU 可以做到逻辑上的并行,虽然无法做到物理意义上的并行。
  • 程序并不知道未来运行的计算机是单核还是多核,所以在编写的时候肯定是尽量扩大代码的并行比例,以此来最多程度利用硬件资源。
  • 即便是运行在单核 CPU 上,多线程中其中一个线程执行缓慢或被阻塞(等待 I/O),其他的线程就可以充分利用阻塞的时间去做其他的事情,让程序可以高效的执行。

是什么让并发和并行成为可能?

  • CPU 升级(摩尔定律还没失效之前)
  • 操作系统升级(划分时间片)
  • 编程语言的升级

高并发只是并发的升级版吗?

什么是高并发?
 同时有很多请求发送给服务器系统,服务器就会并行处理。

* 双11:天猫每秒钟订单的创建峰值高达 49.1 万笔。
* 微信摇一摇:春晚 72亿次,峰值每分钟 8.1 亿次,同时送出了2.1亿个红包。
* 12306的PV值最高297亿次,售出1000万张票。

高并发和多线程的联系和不同?

  • 高并发指的是大量请求同时到达服务器的状况。
  • 多线程是一种解决方案。解决高并发带来的线程安全问题、性能问题。
  • 多线程是应对高并发的一种重要的解决方案,这种解决方案如果处理不当会衍生出更多的问题。(金融系统、订单系统 后果很严重)
  • 面对高并发并不一定要通过多线程这种方法去解决。(用 reids 去解决 MySQL 的高并发)
  • 高并发并不意味着是多线程:Redis(底层用单线程处理,吞吐量也很大、并发也很高)
  • 高并发的解决方案有很多种
* 线程安全问题(HashMap ==>  ConcurrentHashMap)
* 提高硬件的利用率 (tomcat 内部采用多线程模型 来响应 多个请求)

高并发有哪些指标?

  • QPS(Queries Per Second):每秒查询数,每秒请求数。
  • 带宽:网络带宽。
  • PV(Page View):24 小时页面访问量。
  • UV(Unique Visitor):24 小时页面的用户访问数量。(一个用户访问 10 次网站,PV + 10、UV +1)
  • IP 和 UV 的区别
* 如果是同一个cookie,IP变了,IP +1 , UV 不变。
* 同一个IP中会有多个不同的 cookie,IP 不变,UV + N。
  • 并发连接数(The number of concurrent connections):某个时刻服务器所接受的请求个数。通常会大于在线用户数量,因为一个用户可以通过发起多个不同请求。
  • 服务器平均请求等待时间(Time per request: across all concurrent request):服务器处理一个请求所需要消耗的时间。

同步与异步、阻塞与非阻塞

  • 同步与异步:被调用者是否能立刻返回。(服务器的行为)
  • 阻塞与非阻塞:调用者在服务器未返回之前是否能做别的事。(发送方的行为)
* 同步阻塞:盯着水壶烧水,水壶沸腾了也不会发出声音。
* 同步非阻塞:每个几分钟看一下烧水,水壶沸腾了也不会发出声音。
* 异步阻塞:盯着水壶烧水,直到水壶沸腾了会发出声音。
* 异步非阻塞:可以做别的事情,水壶沸腾了会发出声音。

线程分类

 守护线程的作用:jvm 垃圾清理线程。
尽量少使用守护线程,因其不可控性(譬如:执行任务到一半,所有用户线程都运行结束了,这时守护线程就会随着 JVM 一起退出。),如果一定要使用守护线程,也一定不要在守护线程里去进行读写操作、执行计算逻辑。(因为守护线程对于用于来说是不可控的)

  • 用户线程:只要用户线程没有执行完毕,main 程序就无法退出。
  • 守护线程:任何一个守护线程都是整个程序中所有用户线程的守护者,只要有活着的用户线程,守护线程就活着。当 JVM 实例中最后一个用户线程结束时,也随 JVM 一起退出。
/**
 * 守护线程Demo
 */
public class DaemonThreadDemo implements Runnable{
    @Override
    public void run() {
        while (true) {
            System.out.println(Thread.currentThread().getName());
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new DaemonThreadDemo());
        thread.setDaemon(true); //一定要在start()方法之前设置为守护线程
        thread.start();
        Thread.sleep(2000L);
    }
}

作者:Soulboy