纯意识流:力求做到理解,不求做到精确。若有错漏欢迎指正。
首先 我们来理解一些简单的概念。
Ø进程:打开的应用程序。比如QQ这就是一个进程
•独立性:每个进程拥有自己独立的资源,拥有私有的地址空间,相互不可见。
•动态性:进程中有时间、状态、生命周期等动态的概念。
•并发性:多个进程在单个处理器上并发执行。
Ø线程:一条顺序执行的方法。比如简单的main方法 这就是一个线程
•一个进程内部可能包含了很多顺序执行流,每个顺序执行流就是一个线程。
现在操作系统大多使用抢占式多任务操作策略,以支持多进程的并发性,而多线程是多进程的扩展,使一个进程也能像一个处理器一样并发处理多项任务,线程就是进程中并发执行的基本单位,线程也因此被称为“轻量级进程”。一个进程可以包含多个线程,每条线程都有其父进程。
Ø多线程:多条顺序执行的方法。N个方法一起跑
多线程的优点
•能适当提高程序的执行效率
•能适当提高资源利用率(CPU、内存利用率)
多线程的缺点
•开启线程需要占用一定的内存空间(默认情况下,主线程占用1M,子线程占用512KB),如果开启大量的线程,会占用大量的内存空间,降低程序的性能
•线程越多,CPU在调度线程上的开销就越大
•程序设计更加复杂:比如线程之间的通信、多线程的数据共享
在单核 CPU 中,将 CPU 分为很小的时间片,在每一时刻只能有一个线程在执行,是一种微观上轮流占用 CPU 的机制
为什么要用多线程?
多线程会存在线程上下文切换,会导致程序执行速度变慢,即采用一个拥有两个线程的进程执行所需要的时间比一个线程的进程执行两次所需要的时间要多一些。
结论:即采用多线程不会提高程序的执行速度,反而会降低速度,但是对于用户来说,可以减少用户的响应时间。
1、资源利用率高
充分利用计算以及内存资源
2、程序响应更快
3、用户体验更好
支付订单:扣除余额——修改订单状态——修改产权——添加积分——赠送卡券——通知店铺——发送短信——返回成功
支付订单:扣除余额——修改订单状态——返回成功
——修改产权——添加积分——赠送卡券——通知店铺——发送短信
什么是同步?什么又是异步?
同步和异步通常用来形容一次方法调用;
同步:同步方法调用一旦开始,调用者必须等到方法返回后,才能继续后续的行为
异步:一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作
并发(Concurrency)和并行(Parallelism)
并行性是指两个或多个事件在同一时刻发生。
而并发性是指连个或多个事件在同一时间间隔内发生。
什么是阻塞(Blocking)和非阻塞(Non-Blocking)
阻塞和非阻塞通常用来形容多线程间的相互影响。
比如一个线程占用了临界区资源,那么其他所有需要这个而资源的线程就必须在这个临界区中进行等待。
等待会导致线程挂起,这种情况就是阻塞。
临界区:用来表示一种公共资源或者说是共享资源,可以被多个线程使用。但是每一次,只能有一个线程使用它,一旦临界区资源被占用,其他线程要想使用这个资源,就必须等待。
比如,办公室里有一台打印机,打印机一次只能执行一个任务。
如果小王和小明同时需要打印文件,很显然,如果小王先下发了打印任务,打印机就开始打印小王的文件了,小明的任务就只能等待小王打印结束后才能打印,这里的打印机就是一个临界区的例子。
关于事务的举例:【研发部约定每天迟到的人给HRBP(王丰)发10元,作为大家零食基金】
我迟到了要给王丰交钱
我工资卡中有10元,王丰有90元。
•
1.扣除我的余额10元。
2.添加王丰的余额10元。
如果出现以下情况怎么办?
•1成功2失败
•1失败2成功
•我媳妇在我交钱的同时从我工资卡中花了10元买西瓜了
以上的情况会触发事务问题。
事务的特性
一.原子性【要么都成功,要么都失败】
二.一致性【转帐前总金额100,那么转账后总金额也是100】
三.隔离性【我们的转账不影响别人的转账】
四.持久性【转账后的结果是持久的】
实现方式:锁
1.在执行转账前将我和王丰的帐户都冻结。
2.将我的余额变为0
3.将王丰的余额变为100
4.如果中间任何一步失败,将账户变为冻结之前。提示交易失败。
5.如果成功将我和王丰的帐户都解冻。提示成功。
死锁(Deadlock)、饥饿(Starvation)和活锁(Livelock)
死锁:两个或者两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。
饥饿是指某一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行
死锁的4个必要条件
1.互斥条件:一个资源每次只能被一个线程使用;
2.请求与保持条件:一个线程因请求资源而阻塞时,对已获得的资源保持不放;
3.不剥夺条件:进程已经获得的资源,在未使用完之前,不能强行剥夺;
4.循环等待条件:若干线程之间形成一种头尾相接的循环等待资源关系。
如何避免死锁
•有序资源分配法:指定获取锁的顺序
•银行家算法: 请自行了解
银行家算法在避免死锁角度上非常有效,但是需要在进程运行前就知道其所需资源的最大值,且进程数也通常不是固定的,因此使用有限,但从思想上可以提供了解,可以转换地应用在其他地方
饥饿:指某一个或者多个线程因为种种原因无法获得所需要的资源,导致一直无法执行
1)它的线程优先级可能太低,而高优先级的线程不断抢占它需要的资源,导致低优先级的线程无法工作。在自然界中,母鸡喂食雏鸟时,很容易出现这种情况,由于雏鸟很多,食物有限,雏鸟之间的食物竞争可能非常厉害,小雏鸟因为经常抢不到食物,有可能会被饿死。线程的饥饿也非常类似这种情况。
2)另外一种可能是,某一个线程一直占着关键资源不放,导致其他需要这个资源的线程无法正常执行,这种情况也是饥饿的一种。
所以:与死锁相比,饥饿还是有可能在未来一段时间内解决的(比如高优先级的线程已经完成任务,不再疯狂的执行
活锁:指的是任务或者执行者没有被阻塞,由于某些条件没有满足,导致一直重复尝试—失败—尝试—失败的过程。
处于活锁的实体是在不断的改变状态
活锁有可能自行解开。
解决协同活锁的一种方案是调整重试机制。
•比如引入一些随机性
•比如约定重试机制避免再次冲突
锁
公平锁/非公平锁
公平锁:尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。
非公平锁:即无法保证锁的获取是按照请求锁的顺序进行的
独享锁/共享锁
独享锁是指该锁一次只能被一个线程所持有。比如写数据库。
共享锁是指该锁可被多个线程所持有。比如读数据库
互斥锁/读写锁
上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现
乐观锁/悲观锁
乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。
乐观锁总是认为不会产生并发问题,不加锁,而是在提交时判断该次提交是否冲突。
悲观锁总是假设最坏的情况,所以都会加锁(读锁、写锁、行锁等),当其他线程想要访问资源,都需要阻塞挂起。
加锁,解锁都需要消耗性能
Java中使用了偏向锁/轻量级锁/重量级锁
•偏向锁【一个线程多次获取,设置偏向,不进行加锁解锁】
•轻量级锁【多个线程交替获取,不阻塞其他线程】
•重量级锁【多个线程同时获取,阻塞其他线程】
•
•自旋锁【锁的持有时间比较短,与线程阻塞造成的线程切换时间大体相同】
当前线程竞争锁失败时,打算阻塞自己
不直接阻塞自己,而是自旋(空等待,比如一个空的有限for循环)一会
在自旋的同时重新竞争锁
如果自旋结束前获得了锁,那么锁获取成功;否则,自旋结束后阻塞自己