Java并发编程(二)—线程的本质

前言

Java是一种多线程语言,从一开始就支持了多线程开发,在我们的应用程序中也难免会用到线程去解决一些问题。使用多线程会不会是程序变快呢?如今大多数计算机都是多核处理器,将程序分布到不同的处理器去处理自然会加快程序的运算速度,使程序更快的响应。但是在单处理器中多个线程运行时,还会产生上下文切换的开销,这个时候程序会更快的运行吗?答案是在单处理器情况下程序也会更快的运行,因为线程有一种状态叫做阻塞。如果某个线程阻塞以后切换到其他线程继续运行,就会解决等待阻塞的时间。这就是多线程的力量体现。

进程与线程

先说概念:
进程是由程序代码和相关数据以及进程控制块(Process Control Block)组成的程序实体。在操作系统中,一个任务就对应着一个进程,比如打开浏览器、开启QQ等。
进程控制块结构

结构 描述
标识符 跟这个进程相关的唯一标识符,用来区别其他进程
状态 新建、就绪、运行、阻塞、退出
优先级 相对于其他进程的优先级
程序计数器 程序中即将被执行的下一条指令地址
内存指针 包括程序代码和进程相关数据的指针,还有和其他进程共享内存的指针
上下文数据 进程执行时处理器的寄存器中的数据
I/O 状态信息 包括显示的I/O请求、分配给进程的I/O设备(如磁盘驱动器)和被进程使用的文件列表等
记账信息 可能包括处理器时间总和、使用的时针数总和、时间限制、记账号等

线程 是由进程分配资源并且运行在进程所拥有的地址空间中的一个程序任务。进程中所有的线程共享改进程的状态和资源,它们在同一块地址空间中,并且可以访问进程中相同的数据。同时,每个线程也拥有自己的栈存储数据。

线程分类:
用户级线程: 运行在用户级线程软件中,线程的管理有应用程序完成,内核意识不到线程的存在。所有的线程都在进程的用户空间中,线程切换不需要内核态特权,线程的调度也有应用程序负责。当一个进程中的线程运行时产生阻塞操作,那么这个时候内核就会启动阻塞操作并把当前进程的状态置为阻塞态。

内核级线程: 运行在内核级软件中,有关线程的管理在内核中完成,内核为进程及其内部的线程维护上下文信息,应用程序部分没有进行线程管理的代码。

用户级线程和内核级线程的优缺点:

  • 用户级线程切换不需要内核切换状态,二内核级线程切换需要内核切换状态。
  • 用户级线程可以在任何操作系统中运行,不需要修改底层内核。
  • 用户级线程阻塞时,也会阻塞当前进程和其他线程;内核级线程阻塞时不会阻塞当前进程和线程,并且可敬将控制权调度到另外一个线程。
  • 用户级线程的应用程序无法使用多核,一个进程只能分配给一个处理器;内核级线程的应用中,内核可以将同一个进程中的多个线程分配到多个处理器

Java中的线程

在Java中,线程如何实现是根据Java虚拟机如何映射到操作系统来说的。一般来说,我们使用的Java线程是以1:1的形式映射到轻量级进程中去的。轻量级进程是通过内核线程中的接口实现的,它和内核级线程也是1:1的关系,所以从根本上来说,Java线程是一种内核级线程。

线程的基本机制

线程的状态:
在线程的整个生命周期中可以分为6种状态,包括:新建、运行、阻塞、等待、超时等待、终止。
Java并发编程(二)—线程的本质

通过流程图可以看,在线程创建之后,调用start()方法后线程就开始运行。如果线程调用yield()方法那么CPU就会将控制权转移给其他线程,这个时候线程进入了就绪状态,等待CPU再次调度。如果线程调用wait()方法或者其他线程在此线程中调用join()方法,那个线程就进入了等待状态,直到被唤醒或者调用join()方法的线程结束。等待还有另外一种状态是超时等待意思就是在指定时间后继续运行或者进入就绪状态,比如调用wait(time),join(time)方法,在time时间后线程进入运行或者就绪状态。当线程访问加锁的共享资源时,会进入阻塞状态,直到获取到资源的锁才进入运行或者就绪状态。当线程任务结束后,线程终止。

线程的优先级:
在Thread类中有一个整形的变量priority表示线程的优先级,默认值是5,范围是1~10。按照优先级的概念,优先级越高的线程得到的CPU时间越多。但是在Java虚拟机中也会存在差异甚至会忽略优先级的设置,所以不能依赖优先级做一些线程顺序的操作。

线程的休眠:
当线程调用sleep()方法时就进入了睡眠也就是等待状态,在指定时间后进入运行或者就绪状态。调用sleep()需要处理InterruptException异常。

线程的让步:
线程的让步就是让出CPU资源,也就是调用yield()方法。当调用此方法时,就表示当前线程工作将要完成,可以让别的线程使用CPU了。

线程的等待:
线程等待有两种一种是wait()一种是wait(time)。区别就是当不指定时间时,线程就一直处于等待状态,直到被唤醒,比如调用了notify()方法。指定时间时就表示在指定时间过后还没被唤醒就进入运行或者就绪状态。

线程的加入:
Java线程中的join()方法的含义就是当一个线程调用了另外一个线程的join()方法时,则此线程需要等待另外一个线程终止后才能返回。我们在查看join()源码的时候可以发现在join()方法中调用了wait()方法,从而导致调用join()方法的线程阻塞。当线程结束时,就会从join()方法中跳出。

线程间通信:
在java内存模型中,我们知道线程之间可以通过共享内存变量来进行通信。但当要保证线程之间的数据同步时,我们需要对数据进行上锁,这里就要用到volatile和synchronized关键字或者其他锁,同时,它们也担负起了线程之间进行通信的任务,当对数据进行读写是,每个线程都要得知是否另外的线程在操作数据,即对数据获取锁或者释放锁。

总结

以上就是对线程的介绍,从线程在操作系统中的存在以及在Java虚拟机中的存在进行了描述。我们可以知道,Java虚拟机帮我们封装了线程类,隐藏了不同操作系统中的线程的不同实现,也印证了Java一次编写导出运行的特点。

线程作为并发编程的基础了解线程的运行机制是必要的基础。