初步探讨线程问题

一、线程与进程

在了解线程前,首先我们先了解一下什么叫进程。

进程:进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。简单来说,一个应用程序的运行就可以被看做是一个进程。

   狭义定义:进程是正在运行的程序的实例(an instance of a computer program that is being executed)。
  广义定义:进程是一个具有一定独立功能的程序关于某个数据集合的一次运行活动。它是操作系统动态执行的基本单元,在传统的操作系统中,进程既是基本的分配单元,也是基本的执行单元。
  进程的概念主要有两点:第一,进程是一个实体。每一个进程都有它自己的地址空间,一般情况下,包括文本区域(text region)、数据区域(data region)和堆栈(stack region)。文本区域存储处理器执行的代码;数据区域存储变量和进程执行期间使用的动态分配的内存;堆栈区域存储着活动过程调用的指令和本地变量。
第二,进程是一个“执行中的程序”。程序是一个没有生命的实体,只有处理器赋予程序生命时(操作系统执行之),它才能成为一个活动的实体,我们称其为进程。进程是操作系统中最基本、重要的概念。是多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律引进的一个概念,所有多道程序设计操作系统都建立在进程的基础上。
 
线程:线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
  线程是独立调度和分派的基本单位。同一进程中的多条线程将共享该进程中的全部系统资源,如虚拟地址空间,文件描述符和信号处理等等。但同一进程中的多个线程有各自的调用栈(call stack),自己的寄存器环境(register context),自己的线程本地存储(thread-local storage)。
  一个进程可以有很多线程,每条线程并行执行不同的任务。

注意点:

  线程是进程的组成部分,一个线程必须有一个父进程
  一个程序运行后至少有一个进程,一个进程至少要包含一个线程
  线程是操作系统进行调度的单位
  线程不拥有系统资源,多个线程共享父进程的资源
  单线程的程序,只有一个执行流
  多线程的程序,有多个执行流
  多线程可以让一个应用程序的多个顺序执行流同时执行(宏观)
  多个线程是相互独立的、并发执行的
  随机执行的
  使用java命令运行Java程序一次,就是在操作系统中启动一个JVM进程,Java程序的线程都在JVM进程中

 二、并行和并发

  进程只是一个静态的概念,机器上的一个.class文件,机器上的一个.exe文件,这个叫做一个进程。程序的执行过程都是这样的:首先把程序的代码放到内存的代码区里面,代码放到代码区后并没有马上开始执行,但这时候说明了一个进程准备开始,进程已经产生了,但还没有开始执行,这就是进程,所以进程其实是一个静态的概念,它本身就不能动。平常所说的进程的执行指的是进程里面主线程开始执行了,也就是main()方法开始执行了。进程是一个静态的概念,在我们机器里面实际上运行的都是线程。

  Windows操作系统是支持多线程的,它可以同时执行很多个线程,也支持多进程,因此Windows操作系统是支持多线程多进程的操作系统。Linux和Uinux也是支持多线程和多进程的操作系统。DOS就不是支持多线程和多进程了,它只支持单进程,在同一个时间点只能有一个进程在执行,这就叫单线程

  CPU难道真的很神通广大,能够同时执行那么多程序吗?不是的,CPU的执行是这样的:CPU的速度很快,一秒钟可以算好几亿次,因此CPU把自己的时间分成一个个小时间片,我这个时间片执行你一会,下一个时间片执行他一会,再下一个时间片又执行其他人一会,虽然有几十个线程,但一样可以在很短的时间内把他们通通都执行一遍,但对我们人来说,CPU的执行速度太快了,因此看起来就像是在同时执行一样,但实际上在一个时间点上,CPU只有一个线程在运行。

学习线程首先要理清楚三个概念:

  1. 进程:进程是一个静态的概念
  2. 线程:一个进程里面有一个主线程叫main()方法,是一个程序里面的,一个进程里面不同的执行路径。
  3. 在同一个时间点上,一个CPU只能支持一个线程在执行。因为CPU运行的速度很快,因此我们看起来的感觉就像是多线程一样。

  什么才是真正的多线程?如果你的机器是双CPU,或者是双核,这确确实实是多线程。

注意点:

并行
  在同一时刻,同时执行
  多核CPU
并发
  在同一时间段,快速轮换、交替执行
  在某一时刻只有某一个或某几个在执行,在宏观上具有同时执行的效果
  单核、多核

三、创建和启动线程

  线程对象必须是Thread类或Thread子类的对象
  运行Java程序的main方法,会启动一个主线程,主线程的线程执行体就是main方法的方法体
  一个线程可以启动其他的线程
  主线程的名字默认是main
  其他线程的名字按创建顺序默认是Thread-0、Thread-1、。。。

3.1、Thread子类 

  1.定义Thread类的子类,重写从Thread继承的run方法,
 在方法体中编写当前线程要完成的任务(要执行的一段代码),
 run方法的方法体叫做线程执行体
  2.创建Thread子类的对象,得到一个线程对象
  3.调用线程对象的start()方法,启动当前线程
 共享资源(对象)可以时静态的或通过构造方法接收

3.2、Runnable接口

  1.定义Runnable接口的实现类,重写Runnable接口的run方法,
 该run方法的方法体就是线程执行体
 Runnable 用于代表线程的执行体
  2.调用Thread类的构造方法,创建Thread对象时,传入Runnable接口类型的对象
  3.调用线程对象的start()方法,启动当前线程
 共享资源可以作为Runnable接口实现类的成员存在

四、Thread常用方法

String getName()  :返回当前线程的名字

void setName(String name):设置当前线程的名字

static Thread currentThread():返回当前正在运行的线程

static void sleep(long ms):让当前正在执行的线程暂停一段时间,并进入阻塞状态

static void yield():让当前正在执行的线程暂停,并进入就绪状态

void join():加入线程

让当前正在执行的线程等待,等待被加入的线程执行完
阻塞状态

 

 

 五、线程的生命周期

1、新建状态:

  使用new关键字创建了线程对象还未启动,没调start()

2、就绪状态:

   已经调用了线程对象的start()方法  
  线程执行体还没执行,只是表示线程可以运行了,正在等待被(调度)执行
  由操作系统线程调度器决定什么时候运行
  只能用处在新建状态线程调用start(),否则IllegalThreadStateException

3、运行状态:

  线程被调度  获得了CPU使用权,开始执行线程执行体

4、阻塞状态:

  sleep(long ms) ,经过了指定的时间
  阻塞式IO,已经返回
  阻塞状态解除后,会重新进入就绪状态,重新等待线程调度器的再次调度,
  以便接着从停止的地方,继续执行
  获取正在被其他线程持有的同步监视器的锁,获得了锁
  正在等待某个通知,其他线程发出了通知

5、死亡状态

  线程执行体正常执行完
  线程执行体抛出了异常

 

六、线程优先级

优先级高的线程获得执行的机会较多,优先级低的线程获得执行的机会较少

主线程优先级默认是5,其他线程优先级默认与创建它的线程的优先级相同

  Thread
  void setPriority(int priority)
  int getPriority()

优先级的有效值1-10的整数

Thread类中定义的优先级常量:

  MIN_PRIORITY 1

  NORM_PRIORITY 5

  MAX_PRIORITY 10

七、线程同步

同步锁 Lock

 7.1、线程安全问题

多个线程并发修改同一个共享资源,导致的不正确结果问题
线程不安全

7.2、同步代码块

synchronized(obj){
代码块
}
synchronized:
关键字
同步obj:
同步监视器
一个Java对象,每个Java对象中都有一把锁
通常使用要并发修改的共享资源作为同步监视器
一个线程要执行同步代码块,必须先获取同步监视器的锁,对同步监视器上锁
任意时刻,一个对象的锁只能被一个线程获得
一个线程执行完同步代码块,会自动释放占有对象锁
获取锁 --> 加锁 --> 执行 --> 释放锁

7.3、同步方法

修饰符 synchronized 返回值类型 方法名(形参列表){方法体}
在同一时间段内,只能有一个线程在执行同步方法,直至执行完
同步方法的同步监视器是this

7.4、线程会释放同步监视器的锁的情况

同步代码块、同步方法正常执行完

同步代码块、同步方法抛出了异常

同步代码块、同步方法中调用执行了同步监视器对象的wait方法

同步代码块、同步方法中调用执行了sleep、yield、join、阻塞式IO暂停当前线程的执行,并不会释放同步监视器的锁

7.5、死锁

两个线程相互等待对方释放同步监视器锁的现象
避免

八、线程的通信

必须由同步监视器对象来调用,否则java.lang.IllegalMonitorStateException
Object
void wait()
void wait(long ms)
void wait(long ms, int nanos)让当前正在执行的线程释放锁,并在当前监视器上等待,并进入阻塞状态
等待接收到对应监视器对象的通知或等待指定的时间后,进入锁池
void notify()
随机唤醒一个在当前同步监视器上等待的线程
void notifyAll()
唤醒所有在当前同步监视器上等待的线程

九、ThreadLocal<T>

线程本地变量
底层是使用当前线程id作为key的Map
用于隔离多个线程各自的资源,保证当前线程获取到的值一定是自己放进去的
获取线程自己放进去的值
T 要保存的值的类型
方法
void set(T value)
当正在运行的前线程放入一个值
一个线程只能放入一个值,再次放入,覆盖
T get()
获取当前线程放入的值
void remove()
删除当前线程放入的值

 

 

 

 

 

 

posted @ 2019-05-13 17:31  JackieMo  阅读(171)  评论(0编辑  收藏  举报