0%

Java并发编程之线程的概念

基本概念

线程与进程

进程

  1. 用来加载指令、管理内存、管理IO、磁盘读写
  2. 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。
  3. 进程就可以视为程序的一个实例比如java.exe

线程

  1. 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行
  2. 在一个进程里可以创建多个线程,一个标准的线程包括线程ID,当前指令指针(PC),寄存器集合和堆栈组成,堆中的内容可共享。处理器在这些线程上高速切换(涉及到上下文切换),让使用者感觉到这些线程在同时执行。如果没有明确的协同机制,线程将彼此独立执行。每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身。
  3. 线程是进程的一个实体(一个进程包含多个进程),是被系统独立调度和分配的最小单元,,也叫轻量级线程(Light Weight Process),并且线程拥有独立的资源包括:栈、线程ID、寄存器和指令指针,其他堆中的资源在内存中共享。共享资源在并发编程中需要增加同步机制防止资源数据处理不对。
  4. 一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。
  5. 线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。
Java中的线程

在java中,一个应用程序对应者一个jvm实例(jvm进程),一般来说名字默认为java.exe或者javaw.exe。java采用的是单线程编程模型,即在我们自己的程序中如果没有主动创建线程,则只会创建一个主线程。但是需要注意,虽然只是一个线程执行任务,不代表jvm中只有一个线程,jvm在创建实例的过程中,同时会创建很多线程,具体参考如下说明:

一个Java程序的入口是main方法,通过调用main方法开始执行,然后按照代码逻辑执行,看似没有其他线程参与,其实java程序天生就是多线程程序,执行一个main方法其实就是一个名为mian的线程和其他线程分别执行,参考代码如下(来源于java并发编程艺术):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
package com.sunld.thread;
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;
/**
* <p>Description:</p>
* @author sunliaodong
* @version V1.0.0
* <p>CreateDate:2017年9月28日 下午3:54:19</p>
*/

public class ThreadMXBeanTest {

public static void main(String[] args) {
// 获取Java线程管理MXBean
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
// 不需要获取同步的monitor和synchronizer信息,仅获取线程和线程堆栈信息
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
// 遍历线程信息,仅打印线程ID和线程名称信息
for (ThreadInfo threadInfo : threadInfos) {
System.out.println("[" + threadInfo.getThreadId() + "] " +
threadInfo.getThreadName());
}
}
}

返回结果:

1
2
3
4
5
6
[6] Monitor Ctrl-Break
[5] Attach Listener//附加监听
[4] Signal Dispatcher//分发处理发送给JVM信号的线程
[3] Finalizer//调用对象finalize方法的线程
[2] Reference Handler清除Reference的线程
[1] main 线程,用户程序入口

进程与线程的对比

  1. 进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
  2. 进程拥有共享的资源,如内存空间等,供其内部的线程共享
  3. 进程间通信较为复杂
    • 同一台计算机的进程通信称为 IPC(Inter-process communication)
    • 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
  4. 线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
  5. 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低

并行与并发

  1. 并发(concurrent): 同一时间应对(dealing with)多件事情的能力
  2. 并行(parallel): 同一时间动手做(doing)多件事情的能力,会出现资源竞争的问题
  3. 单核: 微观串行,宏观并行,一般会将这种线程轮流使用CPU的做法称为并发
  4. 多核:多核下,每个核都可以调度运行线程,这时候线程可以是并行的

线程的优缺点

线程的优势

如果使用得当,线程可以有效地的降低程序的开发和维护成本,同时提升复杂应用程序的性能。线程能够将大部分的异步工作流转成串行工作流,因此能更好的模拟人类的工作方式和交互方式。此外,线程可以降低代码的复杂度,是代码更容易编写、阅读和维护。

在GUI应用中,线程可以提高用户界面的响应灵敏度,而在服务器应用程序中,可以提升资源利用率以及系统吞吐率。线程还可以简化jvm的实现,垃圾收集器通常在一个或多个专门的线程中运行。

发挥多处理器的强大能力

随着技术的发展,在单核处理器上通过提高时钟频率来提升性能以变的越来越困难,目前大多数机器都是在单个芯片上放置多个处理器核。但是由于基本的调度单位是线程,因此如果使用单个线程会造成很多cpu资源的浪费。多线程程序可以同时在多个处理器上执行,并且可以通过提高处理器资源的利用率来提升吞吐率。比如IO读写。

建模的简单性

通过使用线程,可以将复杂并且异步的工作流进一步分解为一组简单并且同步的工作流,每个工作流在一个单独的线程中运行,并在特定的同步位置进行交互。

可以使用框架实现以上功能,例如:servlet和RMI;框架负责解决一些细节问题,例如请求管理、线程创建、负载均衡,并在正确的时刻将请求分发给正确的应用程序组件。

Java为多线程编程提供了良好、考究并且一致的编程模型,使开发人员能够更加专注于问题的解决,即为所遇到的问题建立合适的模型,而不是绞尽脑汁地考虑如何将其多线程化。一旦开发人员建立好了模型,稍做修改总是能够方便地映射到Java提供的多线程编程模型上。

异步事件的简化处理

服务器应用程序在接受来自多个远程客户端的套接字链接请求时,如果为每个链接都分配其各自的线程并且使用同步IO,那么就会降低这类程序的开发难度。

响应更灵敏的用户界面

使用多线程技术,即将数据一致性不强的操作派发给其他线程处理(也可以使用消息队列),如生成订单快照、发送邮件等。这样做的好处是响应用户请求的线程能够尽可能快地处理完成,缩短了响应时间,提升了用户体验。

线程问题

安全性问题

线程安全性非常复杂,在没有充足同步的情况下,多个线程中的操作执行顺序是不可预测的,甚至会产生奇怪的结果。比如常见的序列中计算(a++),多个线程同时调用会出现重复值的情况。

由于多个线程要共享相同的内存地址空间,并且是并发执行,因此他们会访问或则修改其他线程正在使用的变量。这样就会产生竞态条件(资源竞争),导致并发安全性问题的发生。java本身提供了各种同步机制来协同这种访问。比如加锁以及内存可见性等。

活跃性问题

安全性的含义是“永远不发生糟糕的事情”,而活跃性则是“某个正确的事情最终会发生”。

性能问题

参考

  1. 《Java并发编程的艺术》
  2. Java中的多线程你只要看这一篇就够了
  3. 线程
  4. Java总结篇系列:Java多线程(一)
  5. 《java并发编程实战》
  6. 多线程01:《疯狂Java讲义》学习笔记——线程概述
  7. java并发编程—如何创建线程以及Thread类的使用
  8. Java并发编程:Thread类的使用
  9. Java中断机制
  10. JAVA多线程之中断机制(如何处理中断?)