一. 为什么要使用并发编程?
并发编程的主要目的是为了使程序运行的更快;
但是下一个问题出来了,并发编程一定会使我们的程序运行的更快吗?为什么并发编程会使我们的程序变得更快呢?
二. 理解并发编程
2.1并发编程一定会使我们的程序运行的更快吗?
答案是并不是,并发编程只有在一定的请求量或者计算量的时候才会显示出优势;
2.2 为什么有的时候多线程反而会变慢呢?
我们知道os(操作系统)执行并发操作是采用的时间片轮转算法,简单的说就是线程1先执行10ms 然后线程B在执行10ms 然后线程A又执行了10ms 线程A执行完毕 线程B又开始执行到结束
我们可以发现这种执行的机制,会导致cpu一会执行A线程 一会执行B线程,这就导出了一个第一个导致多线程慢的因素:线程的上下文切花需要时间
补充:Java创建多线程需要OS的干预,OS需要从用户态切换到核心态 这个过程很耗费时间
那么简单的理解就是:如果多线程执行的速度快于单线程执行的速度,那么我们可以理解为多线程一定会在执行前做一些准备,而单线程不需要,所以当任务数或者工作量并不大的时候单线程就会很有优势;但是当任务数上来后多线程的优势就体现出来了;
这里我们有个可以继续深入研究的点:即具体什么时候,什么情况下单线程执行优于多线程
2.3 那么如何提高并发编程的性能呢?
我们知道并发性能的瓶颈就是频繁的上下文切换,那么我们就可以通过减少上下文的切换来提高性能。
减少上下文切换的方法又一下几种
1. 采用无锁并发编程:多线程竞争锁的时候就会引起上下文的切换,所以处理数据的时候应该避免加锁,可以把数据分段,每个线程来处理一段的数据,
2. CAS算法: Java的Atomic包使用CAS算法来更新数据,不需要加锁
**3. 尽量合理的配置线程即使用最少的线程:**例如我们处理一个任务10个线程100ms搞定,那么你开100000个线程来处理,光上下文切换的时间都要超过100ms
**4. 使用协程:**在单线程里实现任务的调度,并在单线程里维持多个任务的切换
2.2 为什么并发编程会使我们的程序变得更快呢?
首先对于单线程来说,程序是一行一行的执行的,所以假设在120行-180行之间的程序含有数据库操作需要等待很长时间,那么180行以后的代码都必须等到180行的代码执行完才能继续执行,而并发编程其实就是异步化,也就是主线程在执行到120行的时候,120-180行的代码交给另一个线程去执行,主线程可以继续向下执行,所以两件事是并行的,所以程序的响应时间就会变快
三.死锁
死锁是导致系统功能不可用的重要原因之一;
什么是死锁呢?
线程1在等待线程2释放锁,线程2也在等线程1释放另一个锁,最终导致线程1和2都在等待中,从而不能继续执行程序;
构成死锁的必要条件有一下几个
- 互斥
- 不可剥夺
- 保持申请
- 循环等待
互斥:也就是加互斥锁,线程1获取到了对资源1的锁,那么线程2就不能获取对资源1的锁了
不可剥夺 线程1对资源1加的锁,线程2不可以撤销线程1对资源1加的锁
保持申请 线程1一直持有对资源1的锁
循环等待 线程1等线程2 线程2等线程1
代码行 | 线程1的操作 | 线程2 的操作 |
---|---|---|
100行 | 对资源1加锁 | 其他操作 |
110行 | 执行其他操作 | 对资源2加锁 |
120行 | 获取资源2 因为有锁所以等待 | 获取资源1 因为有锁所以等待 |
121行 | 因为线程在等待 所以此行不会执行 | 因为线程在等待 所以此行不会执行 |
我们可以看到线程1和2都在120行处等待,所以下面的代码不会执行, 这就是死锁
3.1 如何避免死锁呢?
- 避免一个线程同时获取多个锁
- 避免一个线程同时占用多个资源
- 尝试使用定时锁来代替内部的锁机制
- 对于数据库锁,加锁和解锁必须在同一个连接内,否则解锁会失败
四.多线程需要考虑的问题
即使我们使用了多线程,但是系统还是会受到硬盘的读写速度,网络带宽,cpu处理速度等的影响
所以在设置线程数的时候也要综合考虑