并发编程-线程,JMM,JVM,volatile

1.线程

  相信大家对线程这个名词已经很不陌生了,从刚开始学习java就接触到线程,先说说进程吧,进程就是系统分配资源的基本单位,线程是调度cpu的基本单位,进程由线程组成,一个进程至少又一个线程组成,线程寄生于进程。一个完整的线程由一个程序计数器,一组寄存器,和堆栈组成。其中程序计数器记录了线程下一个要执行的指令,寄存器保存这线程的一些变量,堆栈保存着线程当前的执行状态。

  下面来说说线程的分类。看从不同的角度来分吧。从进程的角度来看,线程分为普通线程和守护线程。普通线程不用多说,基本的程序计数器,一组寄存器,和堆栈,普通线程拥有着正常的生命周期,但是守护线程他的生命周期很长,他是在一个进程中的所有普通线程都进入销毁生命周期的时候,守护线程才会进入生命的结束。下面举例来讲一讲守护线程的应用场景,JVM又一个内存回收机制也就是GC,他是贯穿JVM中所有普通线程的执行到销毁的生命周期,JVM的普通线程都执行完毕的时候,这个时候垃圾回收线程(守护线程)就销毁了,这个时候JVM也就可以退出了,但是如果垃圾回收线程不是守护线程,那么JVM会永远运行着。前面是从进程的角度来说的,如果从cpu调度的角度来说,线程可以分为用户线程(User Level Thread-ULT)和内核线程(Kernel Level Thread-KLT)。我们常说线程从用户态转化成内核态,下面内容会详细讲。

  下面来详细的讲讲用户级线程和内核级的线程,也就是ULT和KLT。操作系统将内存空间分成了用户空间和内核空间,一般以4G的内存大小来说的话,内核空间大概占到一个G左右。顾名思义,内核空间运行着内核级线程,用户空间运行着用户级线程。在操作系统中,只有内核级线程拥有调度cpu的权限,像JVM或者其他软件运行的线程是没有直接使用cpu的权限的。下图1-1是用户空间和内核空间线程使用cpu过程的示意图:

                    

                            图1-1

   首先操作系统将使用cpu分为几个权限角色,以Linux为例,之前有ring0到ring3四个权限等级,现在只有ring3和ring0这两个权限角色,其中ring0拥有使用cpu的权限,ring3没有。从图中可以知道,ULT对应的权限是ring3,KLT对应的是ring0,也就是只有KLT才能有使用cpu的权限,所以当用户空间的线程想要使用cpu必须通过转换接口完成线程从用户态转为内核态,才能由系统根据他的调度算法去调度内核线程,并获取到cpu的时间片。值得注意得失java在1.2版本之前创建的线程都是基于用户态的线程,在1.2之后java创建的线程底层都是内核级的线程。

2.线程的生命周期

  线程的生命周期相信很多java的程序员一口就能答得上来:创建,等待,就绪,运行,阻塞,销毁。其中线程的创建主要有四种方式:继承Java的Thread,实现Runnable接口,还有实现ExecutorService等等,后面的博文会详细介绍。下图2-1是我画的线程的一个生命周期图。

                                   

                                                  图2-1

2.并发

  下面来简单讲讲并发,主要分三个问题来说,第一个什么是并发,第二个是为什么用到并发,第三个是并发存在的问题。

  什么是并发,就是在一个操作系统中,同一时间段中有多个线程在cpu中处于运行的状态,在这里我要说的是,对于单核cpu和多核cpu或者多cpu的系统来说是不一样的。单核cpu严格意义上来讲,他在一个时间点上,cpu只可能运行一个线程,对于多核和多cpu的系统来说,在统一时间点上,他是有可能运行多个线程的。但是从宏观上来讲,由于cpu的速度很快,不管是单核还是多核亦或是多cpu的系统,它的都是并发的,就像我们的系统同时听着个和看着电影,你是感受不到的,从宏观感受上来说,这几个程序都是并发执行的。

  为什么要用到并发,有几方面的原因,对于多核cpu或者多cpu的系统来说,并发能有效的提高cpu的利用率,不至于让cpu出现大量空闲的状态。再就是方便我们对业务进行拆分,提升整个应用的性能等等。

  并发也存在一些问题,用到并发就必然要引出线程上下文切换的概念,线程上下文简单来说就是线程当前的运行状态。它包括线程的指令,程序指针,中间数据等。高并发的场景下,必然会导致线程上下文的高频切换,系统回去花费一部分资源和时间,这会导致cpu的效率变低。还有临界区线程安全的问题,容易出现死锁的问题。

  下面我来先来说说线程的上下文切换,如下图2-1:

                                              

                                                                                    图2-1

  如上图所示,当线程A获取到cpu时间片的时候,线程A开始执行,当线程A失去cpu时间片的时候,此时如果线程A还没有执行完,线程A的上下文信息包括指令,程序指针,以及一些中间数据都要保存下来,我们知道,每个线程在内存中都有自己的栈空间,这是JVM来规划的,此时线程A为了给线程B腾出cpu,她必须将线程的中间状态,也就是线程的上下文保存进入内存该线程的栈空间的Tss任务状态段。这样当线程B用完cpu,A拿到cpu时间片的时候,他才能准确的恢复线程上次执行的状态。

  接下来说说临界区线程安全-死锁的问题,就通过一个小的Demo来演示一下,如下程序所示:

                        

原文地址:https://www.cnblogs.com/jishuzhaiachong/p/11289401.html