多线程

进程与线程

进程

是什么?

在linux是用命令ps aux可以查看正在运行的进程

linux下的进程

进程是程序的一次执行过程。在Java中,每次程序运行至少启动两个线程,一个main线程,一个是垃圾收集进程。在比如打开一个浏览器时,系统会创建一个进程,打开一个新的标签页相当于创建了一个线程,一个进程可以拥有多个线程。

线程

是什么?

线程是比进程更小的执行单位,一个进程包含多个线程,线程处理细小的任务,线程被称为是轻量级的线程。

线程生命周期

线程生命周期

新建

new关键字创建了一个线程之后,线程就处于新建状态,

jvm为线程分配内存,初始化成员变量值。

就绪

当线程对象调用了start() 方法之后,该线程就处于就绪状态,就绪态的线程只缺少处理器资源就可以运行了;

jvm为线程创建方法栈和程序技术器,等待线程调度器调度。

运行

就绪态的线程获得cpu资源,开始运行run(),此时线程进入运行状态。

阻塞

线程可以主动或被动放弃cpu资源,进入阻塞状态

  • 线程调用sleep()主动放弃cpu
  • 线程等待io
  • 线程尝试获得一个同步锁,但是该同步锁被其他线程所占用
  • 线程在等待某个通知(notify)
  • 程序调用了线程的suspend()方法将线程挂起。该方法容易导致死锁。
死亡

线程可能有以下3种方式死亡

  • run()或call()方法执行完成,线程正常结束。
  • 线程执行出错,抛出异常
  • 调用stop()方法结束线程,该方法容易产生死锁。

线程安全

若一个程序使用了多个线程,它每次的运行结果和只使用单线程的结果是一样的,那么就称为线程安全的。若一个程序中对同一个变量有读和写两个线程,因为我们不知道这两个线程的执行顺序,先执行读和先执行写的两个进程读出来的结果是不一样的,这就是线程不安全。

例子:多个窗口进行售票,在程序中一个窗口绑定一个线程运行,若没有适当的线程同步措施,可能会造成一张票被售出多次的情况。

线程的同步方法

  • 同步代码块(synchronized)
  • 同步方法(synchronized)
  • 同步锁(ReenreantLock)
  • 特殊域变量(volatile)
  • 局部变量(ThreadLocal)
  • 阻塞队列(LinkedBlockingQueue)
  • 原子变量(Atomic*)

同步代码块(synchronized)

synchronized关键字可以用于方法中的某个区块中,表示只对该区块的资源进行互斥访问。

synchronized(同步锁){
    需要同步的代码
}

同步锁

多个线程使用同一把锁,在一个线程拥有同步锁的时候,该线程就可以进入代码块,其他线程只能在外面等着。

同步方法(synchronized)

public synchronized void methon(){
     需要同步的代码
}

同步锁(ReenreantLock)

public void lock();            // 开启同步锁
public void unlock();   // 释放同步锁

try{
    lock();
    ....
}finally{
    unlock();
}

死锁

死锁是多个线程互相抢占资源的造成的一种僵局,导致线程之间相互等待,若没有外力作用,线程将永远等待下去。

死锁产生的必要条件

若产生死锁,则下面条件必然成立。

  • 互斥条件,即进程对所分配的进程
  • 不可剥夺条件
  • 占有并请求条件
  • 循环等待条件

#### 死锁预防

防止死锁产生的四个必要条件成立即可。

死锁避免

  • 有序资源分配法
  • 银行家算法
  • 顺序加锁
  • 限时加锁

死锁检测

预防和避免死锁系统开销大且不能充分利用资源,更好的方法是不采取任何限制性措施,而是提供检测和解脱死锁的手段,这就是死锁检测和恢复。

死锁检测数据结构:

  • E是现有资源向量(existing resource vector),代码每种已存在资源的总数
  • A是可用资源向量(available resource vector),那么Ai表示当前可供使用的资源数(即没有被分配的资源)
  • C是当前分配矩阵(current allocation matrix),C的第i行代表Pi当前所持有的每一种类型资源的资源数
  • R是请求矩阵(request matrix),R的每一行代表P所需要的资源的数量

死锁检测步骤

  • 寻找一个没有结束标记的进程Pi,对于它而言R矩阵的第i行向量小于或等于A。

  • 如果找到了这样一个进程,执行该进程,然后将C矩阵的第i行向量加到A中,标记该进程,并转到第1步

  • 如果没有这样的进程,那么算法终止

  • 算法结束时,所有没有标记过的进程都是死锁进程。

死锁恢复

  • 利用抢占资源恢复,即临时将某个资源从它的当前所属进程转移到另一个进程,相当于破坏了死锁产生的必要条件的不可剥夺条件。
  • 利用回滚恢复,周期性的将进程的状态进行备份,当发现进程死锁后,根据备份将进程复位到一个更早的,还没有取得所需资源的状态,接着就把这些资源分配给其他进程。
  • 通过杀死进程恢复,直接将一个或多个进程杀死,相当于破坏了循环等待条件。

多线程特性

当线程参与计算时,原始的数据来自内存,在计算过程中,有些数据可能被频繁读取,这些数据被临时存储在寄存器和高速缓存中,当线程完成计算任务后,这些缓存的数据会写回内存。

当多个线程同时读写某个内存数据时,就会产生多线程并发问题,解决这些问题涉及到多线程编程的三个特性:原子性,有序性,可见性。

多线程编程应该满足三个特性:原子性,可见性,有序性。

原子性

一个或多个操作要么全部执行成功要么就都不执行。

可见性

当多个线程访问同一变量时,一个线程修改了这个变量的值,其他线程应该能够立即看到修改的值。

有序性

程序的执行顺序按照代码的先后顺序执行。

多线程控制类

为了保证多线程的三个特性,Java引入了很多线程控制机制:

  • ThreadLocal
  • 原子类
  • Lock类
  • Volatile关键字

ThreadLocal

未完待续~


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 572108581@qq.com

文章标题:多线程

文章字数:1.7k

本文作者:ZSH

发布时间:2019-11-17, 15:51:27

最后更新:2019-12-19, 19:07:30

原始链接:https://zhongshanhao.github.io/2019/11/17/multiThread/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录