什么是并发
CPU,内存,I/O三者之间读写的速度有很大的差异(CPU>>内存>>I/O),CPU的速度是远远大于内存和I/O,因此当CPU的指令执行完时,内存和I/O还在执行,这时CPU就出现空闲状态,效率低下。为了能够提高性能,就把执行的任务进行拆分多个片段,当等待某个片段执行完时,可以先处理其他未执行的片段。就好比当你正在吃饭时,突然来了电话,由于吃饭这个过程比较久,你就可以先不吃饭,去把电话接听完再回来继续吃饭。
并发的主要三个问题
并发主要抽象为三个核心问题,同步、互斥、分工,下面逐个介绍:
1.分工
分工就好比一个项目组共同做一个模块,你就是项目经理,为每个人都分配一个任务,性能是否高效取决与分工是否合理,若你让一个经验不足的组员去担当高难度且复杂的问题,效率肯定不高。
在并发领域里也有相应的设计模式,例如生产者-消费者模式,Thread-Per-Message, WorkerThread都是用来指导如何分工。JDK中也有分工方法,例如Executor、Fork/Join、Future。
2.互斥
在并发编程中,如果多个线程同时操作一个共享变量,那么就会出现不确定性的结果,这时候就需要线程安全,互斥指的是同一个时刻,只运行一个线程访问共享变量。为了解决这个问题,就出现了锁,也就是java中的synchronized,不过添加了锁意味着性能降低,因为每次只能一个线程访问,因此为了提高性能,SDK提供了ReadWriteLock、StampedLock, 除此之外,JAVA中有一些其他的方案,变量不允许修改,例如final、Thread Local、Copy-on-Write。
3.同步
分工已经做好了,就要到执行的阶段,有些任务是相互依赖的,当这个任务没完成,后续任务就无法开启。那么当任务完成后如何通知后续任务开启,这个就是我们所说的同步,即当一个线程执行完任务时如何通知执行后续任务的开工,也就是线程间的协作问题。JAVA SDK中有很多分工方法,例如Executor、Fork/Join、Future,这三个方法既能分工,又能解决线程协作的问题。除了这三个方法外,还有CountDownLatch、CyclicBarrier Phaser、Exchanger这些都可以用来解决线程协作问题。上面提到的技术都是利用管程来解决,因此理解管程模型是非常非常重要的,因为管程是解决并发问题的万能钥匙。
下面是我整理的一个全景图: