线程和进程

目标:

1.什么是进程?什么是线程?

2.线程的生命周期?

3.怎么创建线程?

一、什么是进程和线程

1.1 应用程序

应用程序是指为完成某项或多项特定工作的计算机程序,它运行在用户模式,可以和用户进行交互,具有可视的用户界面。应用程序通常又被分为两部分:图形用户接口(GUI)和引擎。

1.2 进程

进程是操作系统进行资源分配的最小单位。

什么是资源?

CPU、内存空间、磁盘IO都是资源

1.3 什么是线程?

线程是CPU调度的最小单位。线程不能独立存在,必须依赖于进程。

1.4 CPU核心数和线程数的关系?

1.4.1 多核

一个CPU有多个核心。

内核处理器

一个CPU内核可以执行一个线程。

逻辑处理器

同时可以处理8个线程。

1.5 CPU时间片轮转技术

把CPU时间切片,(RR调度),分配给多个线程使用。

CPU 时间片轮转机制是一种抢占式调度算法,即 CPU 会分配给每个进程一个固定时间片,当一个进程的时间片用尽后,系统会打断该进程并分配给下一个进程。这一过程会一直进行下去,直到所有进程都被执行完毕。

实现原理:

1)系统将所有的就绪进程按先来先服务的原则,排成一个队列,

2)每次调度时,把CPU分配给队首进程,并令其执行一个时间片.时间片的大小从几ms到几百ms.

3)当执行的时间片用完时,由一个计时器发出时钟中断请求,调度程序便据此信号来停止该进程的执行,并将它送往就绪队列的末尾;

4)然后,再把处理机分配给就绪队列中新的队首进程,同时也让它执行一个时间片

1.5.1 上下文切换

上下文切换是非常耗费CPU时间的一个操作。一次上下文切换需要耗费20000个CPU周期。

二、CPU调度

CPU调度,涉及并行和并发的概念。

2.1 并行和并发

并行:同时执行不同的任务;

并发:交替执行不同的任务。

两者区别

1、并发:并发在一台处理器上“同时”处理多个任务。
2、并行:并行在多台处理器上同时处理多个任务。

2.2 为什么要使用多线程和高并发?

1)充分利用CPU资源

2)加快用户响应时间

2.2.2 高并发带来的问题

线程并非越多越好?

操作系统限制线程数:Linux: 1000个;Windows:2000个。

为什么需要限制线程数:线程分配需要占据资源(栈空间);

实际开发中,多线程可以使用线程池。

Java天生就是多线程的。

三、创建线程的方式

3.1 Thread类启动

new Thread().start()

3.2 通过Runnable接口启动

public class MyRunnable implement Runnable

3.3 Thread类和Runnable接口区别?

Java对线程的抽象Thread;

Runnable是对业务逻辑的抽象。

3.4 Callable接口

Callable实现Runnable接口,本质上与Runnable是同一种实现。

四、Thread操作

4.1 start

启动线程。new Thread只是创建一个线程对象。只有start以后,才开始真正启动线程。

如果创建线程以后,调用两次start方法,会怎么样?

抛出异常。因为启动线程的时候会判断线程状态。

4.1.1 start与run方法的区别?

start启动线程,与操作系统挂钩。run是用户业务逻辑的处理。

4.2 join方法

A线程中,B线程join,待B执行完成以后,再接着执行A线程。

线程的执行变为串行。

join阻塞当前线程,执行新的线程任务,新任务执行完成后,在继续执行此任务

4.4 停止线程stop

stop方法过期。stop强制把当前线程干掉,不管当前线程是否安全释放资源。

导致线程占用的资源不会正常的释放。

4.4.2 线程中断interrupt

结束线程可以使用interrupt中断。

  • interrupt:对线程进行中断。实际上是给线程设置一个中断标志位。
  • inpterrupted: 也可以判断当前线程是否被中断。
  • isInterrupted: 判断当前线程是否被中断

JDK线程是协作式的,而不是抢占式的。interrupt以后,线程也可以完全不处理中断标志位。只是通知线程中断。

inpterrupted是一个静态方法,设置当前线程中断标志位,线程进入中断以后会将中断标志位重置。

尽量使用interrupt来结束线程

4.4.3 Runnable中怎么判断线程是否需要中断?

Thread.currentThread.isInterrupted() 来判断

线程处于死锁状态是不会理会中断的。

sleep/wait中断时,外部线程发起interrupt操作,当前线程会抛出 InterruptException,但是不会结束线程。(线程资源释放)在异常处理中,由用户确定是否需要中断线程

4.5 线程优先级setPriority

设置线程优先级,线程优先级1~10,默认值是5。操作系统决定,优先级不一定起作用。

4.6 守护线程setDaemon

用户线程。

守护线程:默认false。

守护进程是一种很有用的进程。Linux的大多数服务器就是用守护进程实现的。比如,Internet服务器inetd,Web服务器httpd等。同时,守护进程完成许多系统任务。比如,作业规划进程crond,打印进程lpd等。

所谓守护 线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。

守护线程的finally也不一定会执行

轮询带来的资源消耗,代码不优雅。

五、线程同步

5.1 synchronized

线程安全共享。

synchronized内置锁,在某个时刻只有一个线程访问方法或者同步代码块。

本质:加锁加在对象的对象头上。锁的是对象。

类锁:静态方法上加锁。Class对象加锁。

5.1.1 错误加锁的原因

System.identityHashCode(): 返回原生的hashCode,即使用户的继承类实现了hashCode.

Integer.valueOf返回值是创建了一个新的对象。

synchronized(i):虽然加锁了,但是每次加锁的对象发生了变化,锁在不同的对象上,因此没有启起作用。

需要保证锁的对象不会发生变化

5.2 volatile关键字

volatile保证对象的可见性。一个线程修改了对象的值,这个新值保证可以被其他线程马上看到。

5.3 ThreadLocal

多线程环境下对对象的安全访问。

ThreadLocal为每个线程提供变量副本,实现线程的隔离。

5.3.1 ThreadLocal使用

声明ThreadLocal变量,创建的时候可以指定初始值。

private static ThreadLocal<Integer> threadlocal = new ThreadLocal<Integer>() {

        return 1;

};

获取值get方法:

threadlocal.get()

写入Thread的值:

threadlocal.set()

5.3.2 原理

Map<threadId, Object>

Thread都有一个成员变量 threadlocalMap, 

5.3.2 ThreadLocal内存泄漏问题

ThreadLocal在get方法会清除Thread 回收的键值对,

ThreadLocal持有的对象不能是static,否则对象会出现共享。

 5.3.3 线程多个ThreadLocal怎么储存?

5.4 线程协作

wait/notify、notifyAll

wait和notify的标准范式:

一定包裹在synchronized关键字的范围内。

 

一个线程调用wait方法以后,会把线程自己持有的锁释放掉。

当线程被唤醒以后,会重新去竞争锁。

5.5 等待超时

wait(long timeoutMilSec)

notify:唤醒一个等待线程

notifyALL:唤醒所有的等待进程。

yeild: 让出CPU的执行权,不会释放锁。

sleep: 当前线程休眠一定时间,同样也不会释放锁;

wait: 当前线程进入休眠状态,且释放锁。线程只有唤醒后才能恢复执行。

六、分而治之和归并排序

6.1 Fork-Join

分而治之:把一个大问题分隔成相同的小问题,这个小问题之间无关联。

归并排序、快速排序、二分查找都是采用了分而治之的思想。

6.1.1 归并排序

 实现范式

七、线程的状态

线程的状态也称为线程的生命周期。

初始状态:new一个新的线程对象。并不代表线程真正开始执行。调用start方法以后才开始真正执行。

运行状态:运行态分为就绪态和运行中状态。

运行中:当前线程分配了时间片,拿到了CPU执行权限,进入运行中状态。

就绪态:如果CPU时间片用完了,或者某种原因被CPU剥夺了,或者某种原因放弃了,当前线程进入就绪态。等待操作系统分配时间片。

等待态:wait/join/LockSupport.park,线程进入等待状态。

什么时候从等待状态切回运行态呢?

Object.notify()/Object.notifyAll()/LockSupport.unpark(),可以将线程从等待状态切回运行状态。

如果是等待超时,会自动从等待态切回运行态。

阻塞态:等待进入synchronized,未获取到锁,阻塞等待。

什么时候退出阻塞态?

获取到锁以后,切回运行态。

终止态:线程执行完成。

如果调用显示锁lock,是否进入阻塞态?

不会。一个线程进入阻塞态,只有调用synchronized关键字。显示锁底层使用的是Locksupport。

八、死锁

8.1 什么是死锁?

死锁是指两个或者两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的阻塞现象,若无外力作用,它们都将无法推进下去,此时系统处于死锁状态或者系统产生了死锁。

8.2 发生死锁的条件

发生死锁的三个必要条件:

  1. 多个操作者(M>=2) 争夺多个资源(N》=2),N<=M
  2. 争夺资源顺序不对
  3. 拿到资源不放手

学术化的条件:

  1. 互斥条件
  2. 请求保持
  3. 不剥夺:拿到资源后不能被外界强制剥夺
  4. 环路等待:操作者对资源的等待形成环路。

 8.3 打破死锁的方式

针对资源顺序不会:强制规定争夺资源的顺序;

针对拿到资源不放手:争夺下一个资源失败的情况下,可以释放所有拿到的资源。

8.4 活锁

线程T1和T2 争夺资源A1、A2, T1先拿资源A1、再拿A2; 线程T2先拿资源A2, 再拿A1。两者都采用拿到资源失败时释放拿到的资源。

T1和T2都没有拿到资源,且未产生死锁,这种情况称为活锁。

Sleep一段时间,减少操作者碰撞的概率。

九、线程饥饿

线程优先级太低,老是拿不到CPU执行权。

### Python 中线程进程的区别 在Python中,线程进程都是用于实现并发编程的重要机制,但两者之间存在显著差异。 #### 线程的特点 线程是操作系统能够进行运算调度的最小单位。多个线程可以在同一个进程中共享内存空间其他资源,这使得线程间通信变得简单快捷[^1]。然而,由于全局解释器锁(GIL)的存在,在CPython实现下,即使启动了多线程,某些情况下也无法真正并行执行CPU密集型任务[^5]。 #### 进程的特点 相比之下,每个进程拥有独立的地址空间,这意味着不同进程之间的数据隔离较好,安全性更高。但是这也带来了更高的开销——创建新进程的成本远大于创建新线程,并且跨进程的数据交换相对复杂一些[^3]。 ### 使用场景分析 对于I/O密集型的应用来说,比如网络爬虫或文件处理工具,使用多线程往往能获得较好的效果;而对于计算密集型的任务,则更适合采用多进程方案以充分利用多核处理器的能力[^2]。 ### 实现示例 以下是简单的多线程进程的例子: #### 多线程实例 ```python import threading def print_numbers(): for i in range(5): print(f'Number {i}') thread = threading.Thread(target=print_numbers) thread.start() thread.join() # 主线程等待子线程完成后再继续往下走 ``` #### 多进程实例 ```python from multiprocessing import Process def print_letters(): for letter in 'ABCDE': print(letter) process = Process(target=print_letters) process.start() process.join() # 父进程等待子进程结束之后再继续运行下面的代码 ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值