0.背景
最近重温《并发编程的艺术》这本书,觉得里面有些不错的内容,打算截取一部分作为笔记和大家共同学习。
并发编程的目的是让程序运行的更快。下面是并发编程的一些关键词:
1 上下文切换
即使是单核处理器也支持多线程执行代码,CPU通过给每一个线程分配CPU时间来实现这个机制。时间篇是CPU分配給各个线程的时间,因为通常时间片设置的很短,所以CPU通过不停的切换线程执行,给我们造成线程是同时运行的错觉。
概念: CPU通过时间片分配算法来循环执行任务,当前任务执行一个时间片之后,会切换到下一个任务。在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再次加载这个任务的状态。所以从从保存到再加载的过程就是一次上下文切换。
实践表明当并发累加操作不超过10万数量级以上时,速度会比串行累加操作要慢。为什么呢?这是因为线程有创建和上下文切换的开销
2 如何减少上下文切换
减少上下文切换的方法有: 无锁并发编程、CAS算法、使用最少线程和使用协程。
使用最少线程。 避免创造不需要的线程。
无锁并发编程。 当多线程竞争锁时,会进行多线程切换,所以多线程处理数据时,可以使用一些方法减少使用锁。比如,将数据的ID按照Hash算法取模分段,不同的线程处理不同的数据。
CAS算法。CAS(比较与交换,Compare and swap) 是一种有名的无锁算法。CAS的语义是“我认为V的值应该为A,如果是,那么将V的值更新为B,否则不修改并告诉V的值实际为多少”,CAS是一种 乐观锁 技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
CAS是以原子操作为基础,采用事务->提交->提交失败->重试这样特定编程手法的机制,它使得正在访问共享资源的线程不依赖于任何其它线程的调度和执行,并且能够在有限的步骤内完成。Java的Atomic包使用CAS算法来更新数据,而不需要加锁。协程。 在单线程里实现多任务的调度,并在担心爱你成里维持多个任务之间的切换。
3 死锁
概念: 是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
3.1产生死锁的4个必要条件:
1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
3.2 预防和避免死锁
在系统中已经出现死锁后,应该及时检测到死锁的发生,并采取适当的措施来解除死锁。当然最好的做法是预防和避免死锁。
1)预防死锁:通过设置某些限制条件,去破坏产生死锁的四个必要条件中的一个或者几个,来预防发生死锁。预防死锁是一种较易实现的方法,已被广泛使用但是由于所施加的限制条件往往太严格,可能会导致系统资源利用率和系统吞吐量降低。
2) 避免死锁。
该方法同样是属于事先预防的策略,但它并不须事先采取各种限制措施去破坏产生死锁的的四个必要条件,而是在资源的动态分配过程中,用某种方法去防止系统进入不安全状态,从而避免发生死锁。
避免死锁的常见方法:
避免一个线程同时获取多个锁
避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源。
尝试使用定时锁,使用lock.tryKock(timeout) 来替代使用内部锁机制。
对于数据库锁,枷锁和你解锁必须在一个数据库链接里,否则会出现解锁失败的情况。
注1:CAS(比较与交换,Compare and swap) 是一种有名的无锁算法。CAS的语义是“我认为V的值应该为A,如果是,那么将V的值更新为B,否则不修改并告诉V的值实际为多少”,CAS是一种 乐观锁 技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
CAS是以原子操作为基础,采用事务->提交->提交失败->重试这样特定编程手法的机制,它使得正在访问共享资源的线程不依赖于任何其它线程的调度和执行,并且能够在有限的步骤内完成。