前言
可以将 Java 并发编程抽象为三个核心问题: 分工, 同步和互斥.
这三个问题的产生源自对性能的需求. 最初时, 为提高计算机的效率, 当 IO 在等待时不让 CPU 空闲, 于是就出现了分时操作系统也就出现了并发. 后来, 多核 CPU 出现, 不同的任务可以同时独立运行, 于是就出现了并行[分工] . 有了分工后, 效率得到了很大的提升, 但是为了更合理的安排以及控制任务的进行, 就需要让进程之间可以通信[同步] , 让彼此知道进度的执行. 分工进行提高了效率, 但是却带来了多线程访问共享资源会冲突的问题. 于是对共享资源的访问又需要串行化. 所以, 依据现实世界的做法设计了锁等机制来使得多线程[互斥] 访问共享资源.
分工(性能)
分工的主要工作是: 如何高效拆解任务并分配给线程.
Java SDK 并发包中的 Executor,Fork/Join,Future 本质上都是分工方法.
并发编程中的一些设计模型也是指导如何分工: 生产者 -- 消费者, Thread-Per-Message,Work Thread 等.
同步(性能)
在并发编程的同步, 主要指的就是线程间的协作. 即当一个线程执行完了, 该如何通知后续任务的线程展开工作.
协作一般和分工相关. Java SDK 中 Executor,Fork/Join,Future 本质上是分工方法但是也解决线程之间的协作问题(如 Future 异步调用, get()).Java SDK 里提供的 CountDownLatch,CyclicBarrier,Phaser,Exchanger 也是用于解决线程之间的协作问题.
线程协作问题都可以被描述为: 当某个条件不满足时, 线程需要等待, 当某个条件满足使, 线程需要被唤醒执行.
互斥(正确性 / 线程安全)
互斥指的是: 在同一时刻, 只允许一个线程访问共享变量.
因为 可见性, 有序性和原子性 (后面会有文章介绍) 问题, 多个线程访问同一个共享变量会导致结果的不确定 .
为了解决这三个问题, Java 语言引入了内存模型, 内存模型提供了一系列的规则, 利用这些规则我们可以避免可见性问题, 有序性问题, 但是还不能完全解决线程安全问题.
解决线程安全问题的核心方案还是互斥.
实现互斥的核心技术就是锁. Java 语言中 synchronized,SDK 中的各种 Lock 都可以解决互斥问题, 但是锁却会带来性能问题, 于是我们就需要平衡.
主要方案有: 分场景优化, 优化读多写少场景: ReadWriteLock,StampledLock 以及无锁结构 Java SDK 中的原子类; 其他方案, 原理为不共享变量或者变量只允许读, Java 中提供了 Thread Local 和 Final 关键字和 Copy-on-write 模式.
小结
在看极客时间专栏《Java 并发编程实战》学习攻略时, 感触还是比较深. 平时学习知识都是 "独立" 的, 没有一种 "全局" 观念, 也很少联系其他一些理论来侧面验证学习的知识, 导致学过后就很容易忘记. 看了这篇专栏前言后, 总结出: 学习知识时, 要跳出来看全景, 钻进去看本质. 要知道每一种技术背后都应该有理论支持, 并且这个理论可能是跨领域的, 所以, 掌握技术背后的理论十分很重要!
针对 Java 并发编程应该要结合操作系统一起来学习, 如后面将要介绍的可见性, 有序性和原子性. 理解可见性就需要了解 CPU 和缓存的知识; 理解原子性就需要理解操作系统的知识; 很多无锁算法也是和 CPU 缓存有关. 要联系起 CPU, 内存, I/O 之间的关系.
参考:
[1]极客时间专栏王宝令《Java 并发编程实战》
来源: https://www.cnblogs.com/myworld7/p/12189332.html