1、操作系统中的进程及线程
操作系统的核心内容:进程管理、存储管理、文件系统管理、I/O设备管理
进程运行的实体,包括运行程序中占据的系统的资源,CPU、内存、网络资源,同一时刻运行两次程序是两个进程
一个进程具有多个线程,共享进程资源
进程的管理,主要分为两个方面:进程通信和进程调度。
- 进程通信的例子是进程A产生数据,进程B打印数据,故A,B需要通信。
- 进程调度:只有一个CPU可用,CPU需对下一个进程或线程选择
一个进程的组成:进程控制块,数据段,程序段。进程的控制,通过原语进行控制
2、线程池
线程池的概念: JVM在HotSpot线程模型下,JVM线程会一对一的映射为内核线程,导致的问题,创建以及回收都会去内核去创建即回收,导致时间和资源会花费的多
线程池的目的: 提高线程的高可用性以及固定线程的数量,避免重复的创建
创建:使用ThreadPoolEXecutor,不使用Executors,因为更能了解线程池运行的规则,避免资源耗尽的风险
主要是构造线程池提供了几个参数:
CorePoolSize 核心线程的数量 一般的设置最大的线程数量和核心线程数量相等
maximumPoolSize 最大的线程的数量
keepAliveTime线程空余的时间 是指当前的线程数大于核心的线程数了,到到时间进行回收
workQueue 阻塞的队列
Handler 任务的拒绝策略
任务提交的过程:
- 在提交的时候,首先判断核心线程的数量是否小于CorePoolSIze,如果小于,则直接创建新的线程执行任务 (因为任务是放在线程中进行执行的)
- 如果大于corepoolSize,判断wokeQueue阻塞队列是否已经满了,如果没有满加入阻塞队列
- 如果阻塞队列满了,查看是否大于maixmumSize,如果没有,直接创建一个线程
- 如果大于最大的线程数量,则执行拒绝策略,具体的是自己实现的handler
如何设置线程池中核心线程的数量?
- 查看自己的任务是CPU密集的还是IO密集的,根据情况进行设定线程池中核心线程的数量
如何进行设置数量?
- 在创建线程池的时候,ThreadPoolExcutors线程的实现类中,重点维护一个Ctl参数,用高三为表示线程池的状态,低29为表示线程的数量
3、ThreadLocal (多线程的情况下,实现了数据的隔离)
首先,ThreadLocal是一个类,一般我们定义一个成员的变量,可以实现多个线程进行操作和修改,变量对于多个线程之间并不独立,是共享的变量,使用ThreadLocal将变量进行私有化
ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的,在多线程环境下,如何防止自己的变量被其它线程篡改。
实际的使用场景:Spring采用Threadlocal的方式,来保证单个线程中的数据库操作使用的是同一个数据库连接,同时,采用这种方式可以使业务层使用事务时不需要感知并管理connection对象,通过传播级别,巧妙地管理多个事务配置之间的切换,挂起和恢复。使用map进行存储,key是datasource,value是connection,保证了单个线程只能操作自己的数据源
详细的解释:
ThreadLocal t1 t2
local1 threadlocals
local2
同一个变量local,在线程t1是小红,在线程t2中是小兰,别人额印象才是最重要的
总共包含三个方法:set get remove
怎么去set一个值呢?
ThreadLocal<String> localName = new ThreadLocal();
localName.set("张三");
String name = localName.get();
localName.remove();
主要的原理: Set的时候,是通过ThreadLocalMap实现的,而当创建一个线程的时候,ThreadLocalMap是通过线程的一个变量threadlocals来获得的
public void set(T value) {
Thread t = Thread.currentThread();// 获取当前线程
ThreadLocalMap map = getMap(t);// 获取ThreadLocalMap对象
if (map != null) // 校验对象是否为空
map.set(this, value); // 不为空set
else createMap(t, value); // 为空创建一个map对象 }
```js
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
其中对应的ThreadLocalMap中的key是一个弱引用(肉体已经走了,但是还会想念),只要发生垃圾回收的时候,弱引用指向的对象就会被回收。此时的对应的key就已经回收。则会发生内存泄漏,因为此时的value还一直在。所以一般get\set完的时候使用remove方式进行回收value的值
但对于在线程中使用ThreadLocal这个类时候需要注意,一般的线程是重复利用的,如果不执行remove方法,则容易发生内存泄漏
内存泄漏的条件?
- 弱引用被回收,但值一致还在
- 不执行set、get、remove方法
- 线程池中的线程一直不销毁
4、OS线程之间的通信
常见的进程同步与互斥的机制:信号量与PV操作 管程
- PV操作可以实现进程之间的互斥和同步
- 管程只能实现进程的互斥,对PV操作进行了封装
对于java中也采用管程机制,Synchronized,一次只允许一个线程进行调用
Linux常见的进程通信(IPC interProcess Commucation):
- 管道:将前一个命令的输出作为后一个命令的输入,只能单向流动,半双工通信的方式,借助内核缓冲区实现,不可反复读取,只能传输无格式的字节流
- 消息队列:管道的传输方式不适合频繁的交换数据,放在消息队列中自己去访问,消息队列在内核中 缺点:需要内核的接入
- 共享内存:通过系统调用剪一块共享的内存,但有点浪费空间
- 信号量与PV操作:传递的是进程的状态
- socket套接字:跨网络之间的通信,也能完成同主机之间的通信