线程概念及线程并发

前言

青春渐远 活力不现 但路途未变 希望依存 给疲惫匆忙的过往留下足迹 ---致阿开

该篇主要以重新了解线程定义及复习线程并发

开始罗列线程的概念(之所以要如此罗列,初衷是将线程与数据结构,与系统之间的联系,何谓我是谁,由哪来,打哪去这种哲学级别的境界考虑线程,将其串进你的知识体系与对应位置)

提到线程不得引入进程的概念

进程:计算机中Process是一具有独立功能的程序关于某数据集合上的一次运动活动,系统进行资源分配和调度的基本单位,是操作系统结构的基础。在多线程的系统中,进程则不再是一个具有独立功能的运动活动,只是将资源合理分配给自己的线程集的调度平台。

看到进程的概念后,他就是一次分配和调度资源的一次运动活动。那线程的概念呢?现在开始想我们的线程的概念就是进程的概念呀,没错,线程的处理定义也是如此,这就要提及进程与线程之间的关系,顺带引入线程的由来。
进程是60年代提出来的,它作为资源的独立拥有者被定义,一个简单的操作行为:它操作CPU、GPU执行任务输出结果,存入内存,存入硬盘。CPU执行完,需要等待硬件硬盘数据的写入。这期间,CPU的资源浪费掉了。当CPU开始工作了,硬盘开始闲置了,资源同样浪费掉了。所以在80年代,出现了独立运行的基本单位——Thread。线程的出现,可以粗略的理解为进程的copy。不同之处在于一个系统中,会有一个进程作为分配和调度的对象换成了线程集。线程集中的线程各思其职,这样存在了多个拥有进程职能的线程去处理由进程分配的资源(CPU硬盘之流)。A线程不用CPU了,B线程用,A线程不写硬盘了,B线程写。减少了资源的浪费,也提高了系统的执行效率(这中间会出现资源调度的同步问题,这放在本篇后续来分析)。

线程定义

线程是一个程序是中单一的顺序控制流程。也被称为LWP(Light Weight Process)轻量级进程

现在知道线程其实就是为了解决系统中只存在一个进程出现的资源浪费的问题。线程的产生由来和定义现在清楚了,来看下线程是如何解决资源浪费,提高效率,先来讲一下线程的组成,线程只由相关堆栈(系统栈或用户栈)、寄存器和线程控制表TCB组成。

寄存器的概念

寄存器是中央处理器内的组成部分。寄存器是有限存贮容量的高速存贮部件,它们可用来暂存指令、数据和地址。在中央处理器的控制部件中,包含的寄存器有指令寄存器(IR)和程序计数器(PC)。在中央处理器的算术及逻辑部件中,存器有累加器(ACC)。

这里直接引出寄存器的概念,寄存器可被用来存储线程内的局部变量,但不能存储其他线程的相关变量。在同一进程中的各个线程中,都可以共享该进程中的所有资源,被记录在在PCB(进程的组成部分,Processing Control Block)中,表示进程拥有这些资源或者说这些资源正在被该进程使用。然后由进程分配这些资源该由线程集中的哪一个线程使用,当线程在处理事务,执行任务时,就需要使用到寄存器来执行线程中的指令,存储中间变量等(这时线程因为独立占用寄存器执行各自的任务,多个线程间是可以同步工作的),这样就达到了避免资源浪费,提高了系统的运行效率。

以上的线程基础概念可以理解为CPU相关的线程概念,举例:一cpu为四核处理器,即该cpu拥有4线程。我们通常提及的Java多线程是在JVM的进程中,拿android来说,一个app通常情况下为一个JVM进程,在app中,会出现多个线程同时执行不同的任务,在一个四核手机上,也许一个app同时会有10个线程在同时执行下载任务,这时该手机四条线程都提供给我们自己的app来执行下载任务,也不够的,何况有android系统有N多进程为app提供服务。Java的多线程是系统通过时间片机制,采用线程调度,频繁切换线程执行任务。概念需要了解,这样在使用线程时,我们就可以理解线程的执行环境,清楚线程该如何创建,避免因为线程过少,资源利用不够;线程过多,造成线程间的频繁切换与同步造成的时间损失。一般要取得一系统存在多少线程数最为合理,需要多进程测试,通过不断修改线程数量运行,观察其执行时长来选择最为合适的线程数。

线程的实体包括程序、数据和TCB。线程是动态概念,它的动态特性由线程控制块TCB(Thread Control Block)描述。其中TCB中包含以下几块:
线程状态
当线程被挂起时,被保存的现场资源
一组执行堆栈
存放每个线程局部变量的存储区
访问其所属进程的主存和其它资源
线程执行任务,需要用到的代码即指令即为程序,需要参与的参数数据即为数据。在整个过程中,线程状态的变更,线程变更过程中的临时数据,执行哪里执行指令,如何执行指令则在TCB中,对以上的了解有利于我们在今后使用线程时的理解。

下面来看一看线程的生命令周期及线程状态


Thread.png

图中标识的状态,分为 NEW RUNNING WAITING TIMED_WAITING BLOCKING TERMINATED,分别表示
NEW:线程被创建
RUNNING:线程正在执行
WAITING: 线程处于等待状态
TIMED_WAITING:线程处于等待状态,区别WAITING是有时间限制,时间一过,自动进行RUNNING状态
BLOCKING:阻塞状态
TERMINATED:线程被执行完毕的状态
以上罗列的关于线程相关的概念及线程的由来,最后图中标识了线程的生命周期及各个状态之间切换的触发源头


线程并发

线程并发是为了提高程序在多CPU平台上的运行效率,更好的模拟仿真(例如一个场景下多个自主执行的参照物等)
这里要简单的讲解一些基本概念,其中包括本人工作中不经常用到的部分

Thread:线程,调度器,用来执行我们赋予访类的任务
Runnable:接口,描述要执行的具体任务,没有返回值
Callable:接口,同样作为一种描述要执行的具体任务,但其具有返回值(泛型)
Executor:接口,执行器,另ExecutorService接口继承它.
Executors:创建线程池的接口类

重点提一下Callable接口,该接口的定义(函数式接口)
public interface Callable,泛型接口,只有一个call抽象方法,返回该泛型,该接口的实现类,只能由EexecutorService.submit方法调用,该方法返回一个Future对象,它用Callable返回结果的特定类型进行了参数化.通过Future的get方法取得Callable的call方法的返回值,在call方法没有执行完任务时,get方法会阻塞,可以通过Future的isDone来做时实检测,确定是否调用get方法(因为get方法会阻塞)

有经常用到的四个实现类,分别是FixedThreadPool(可以自定义线程并行运行的个数)CachedThreadPoll(线程池中线程个数为Integer.MAX_VALUE)SingleThreadPool(单一线程池,可以序列化要执行的线程,确保同时只有一个线程执行)ScheduledExecutorService(可以执行调度的线程池,可以对池内的线程做延时与重复执行的操作)
以上四种常用的线程池都是调用Executors类相关new接口创建.
如.newFixedThreadPool;newCachedThreadPool;newSingleThreadExecutor;newScheduledThreadPool.
多提一句,线程池的优点:

1.重用存在的线程,减少对象创建、消亡的开销,提升性能。
2.可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞。
3.提供定时执行、定期执行、单线程、并发数控制等功能。

还是要罗列概念了
线程的join方法(因为我不熟悉,虽然不用呀.)
一个线程可以在其它线程之上调用join方法,效果是等待一段时间直到第二个线程结束,再继续执行.
举例:一个线程A在另外一个线程B上调用A.join,此时,需要B线程让步线程A,需要线程A执行完后,线程B才继续执行

线程的并发,我们一直在享用他的优势,同样,在多线程访问共享资源时,所产生的资源冲突和不确定性.问题的解决思路,其实很难简单,就是避免冲突,消除不确定性.而解决办法则会相对复杂,因为我们不知道是哪里冲突了,哪里存在不确定因素,这里就需要长期的项目经验,来找到病灶,

根据两点,举两个经典的例子.
一是进销存系统(存在进销对一个对象的增减操作)
二是单例模式的DCL(存在创建instance时的不确定性)

进销存系统,一个线程进货,一个线程销货,多个线程进货,多个线程销货,那么冲突的就货物本身,进货的线程从主内存中,取得货物数量假如是2,复制货物清单进自己的cache,然后进货量为3,修改自己的cache为5;此时销货进程同步执行销货1,同理,自身cache中为1(2-1),假如此时锁货进程还未同步1进主内存,进货进程同步5到主内存中,系统中会丢失1,丢失的1就是销货进程销掉的1.此时,因为冲突导致整个系统的资源不同步了.
那解决办法,其实很简单,就是同步,用同步消除并发产生的资源冲突.
解决思路也有很多,主要是就是加锁 synchnoized,Lock.

单例的DCL,在理论上存在一个问题,就是

public class Instance {
    private static Instance instance;
    public static Instance getInstance(){
        if (instance == null){
            synchronized (Instance.class){
                if (instance == null){
                    instance = new Instance();
                }
            }
        }
        return instance;
    }
}

instance = new Instance();
这行代码并非是原子操作,其会执行两指令,一是将Instance对象放到空间中,二是开辟空间给instace引用指向,那么编译器(或者JIT)会在执行指令时,做指令重排序的优化操作,执行顺序可以为一二,也可以为二一.
现在有两个线程A和B,A线程在创建instance,恰巧执行的顺序是二一,当执行二,指向一个空间地址;线程B进入getInstance方法,判断instace == null,不成立,直接返回instance,但是B拿到的instance指向的是一个啥也没有的空地址.这时就有问题了哈.
解决思路也很简单,禁止instance创建时的指令重排序

private static volatile Instance instance;

要说锁和修饰符volatile,要说的内容就太多了,留着以后单独另起一篇文章描述吧.

加油!加油!加油!~~

## 参考

进程:  https://baike.baidu.com/item/进程/382503?fr=aladdin
线程:  https://baike.baidu.com/item/线程/103101?fromtitle=Thread&fromid=5156974&fr=aladdin
寄存器:  https://baike.baidu.com/item/寄存器/187682?fr=aladdin
Java线程与CPU线程:  https://blog.csdn.net/wutongyuWxc/article/details/78732287
线程池:android-25API.及https://blog.csdn.net/xu__cg/article/details/52962991
并发:Thinking in Java 第四版

你可能感兴趣的:(线程概念及线程并发)