在写Java Concurrent之前的铺垫,这一篇有点乱,什么都提了一点。
并发可能在许多刚接触编程的程序员眼中显得高大上或者多余,因为刚接触编程时不是很理解 并发的背景、意义,并且并发编程通常相对于串行执行的程序要复杂一些,相对不容易写对,新手不是很愿意接触。
1、性能是最主要的原因。大约零几年的时候,因特尔正式宣布4GHZ芯片研发计划取消,代表着单核性能提升逐渐缓慢,但同时CPU以多核形式呈现到人们眼前,摩尔定律得到了延续,在这种大环境下,为了充分发挥CPU性能,充分利用CPU资源 并发编程开始火热起来。
2、针对复杂的业务模型,并发编程更适合完成业务需求,比如一个下单流程,存在绑券、入库、生成订单、消息分发等多个操作。我们可以以多线程的形式区分业务模块,并且当这些操作并行执行时,响应时间可以倍数减少。这里多线程并发编程更像是一种解耦策略,它帮助我们把做什么(目的)&什么时候做(时机) 分开,应用程序的吞吐量存在显著的提升。
如果觉着好像没接触过多线程并发编程,其实仅仅可能是不知道而已。Java web中的servlet就是最经典的单例多线程模型,可以尝试着在servlet开一个变量,然后尝试并发访问,如果觉着程序执行太快,不好模拟,尝试一下sleep。
因为性能,因为业务,因为资源,我们也算是被迫选择了并发编程。
刚开始大家可能会对并发编程存在什么误解,比如说
1、并发编程一定能改善性能
2、并发编程使用既有工具组合一下即可(比如concurrenthashmap、lock什么的)
所以在接触并发编程之前,首先需要了解一些基础概念,省的以后的学习过程中出现什么奇妙的看法。
1、先搞清楚业务场景是那种类型
IO密集型:IO(input、output)(磁盘IO、网络IO等)。IO密集型指的是系统的CPU性能相对硬盘、内存要好很多,此时,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高,I/O bound的程序一般在达到性能极限时,CPU占用率仍然较低。这可能是因为任务本身需要大量I/O操作,而pipeline做得不是很好,没有充分利用处理器能力。
CPU密集型:也称为计算密集型,指的是系统的硬盘、内存性能相对CPU要好很多,此时,系统运作大部分的状况是CPU Loading 接近100%,I/O在很短的时间就可以完成,而CPU还有许多运算要处理,CPU Loading很高。
Java 线程:
1)首先要明确一点,Java 代码是在JVM上运行的,然后JVM与操作系统直接交互。就当前阶段而言,Java 线程与操作线程存在什么样的关系需要看JVM具体实现的映射关系,不同的平台通常是不一致的。
2)就当前阶段,Java 实现、Linux 平台来说,都是由一对一映射到操作系统线程的。
java线程状态:
1. 初始(NEW):新创建了一个线程对象,但还没有调用start()方法。
2. 运行(RUNNABLE):Java线程中将就绪(ready)和运行中(running)两种状态笼统的成为“运行”。
线程对象创建后,其他线程(比如main线程)调用了该对象的start()方法。该状态的线程位于可运行线程池中,等待被线程调度选中,获取cpu 的使用权,此时处于就绪状态(ready)。就绪状态的线程在获得cpu 时间片后变为运行中状态(running)。
3.阻塞(BLOCKED):表线程阻塞于锁。
4.等待(WAITING):进入该状态的线程需要等待其他线程做出一些特定动作(通知或中断)。
5.超时等待(TIME_WAITING):该状态不同于WAITING,它可以在指定的时间内自行返回。
6. 终止(TERMINATED):表示该线程已经执行完毕。
同步、异步:
同步方法调用一旦开始,调用者必须等到方法调用返回后,才能继续后续的行为。
异步方法调用更像一个消息传递,一旦开始,方法调用就会立即返回,调用者就可以继续后续的操作。而异步方法通常会在另外一个线程中,“真实”地执行着。整个过程,不会阻碍调用者的工作。
阻塞、非阻塞:
1、阻塞调用是指调用结果返回之前,当前线程会被挂起。函数只有在得到结果之后才会返回。
2、非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该函数不会阻塞当前线程,而会立刻返回。
线程安全性
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么就称这个类是线程安全的。
对象分类
1、有状态对象就是有数据存储功能,就是有实例变量的对象 ,可以保存数据,是非线程安全的。
2、无状态对象就是没有实例变量的对象 .不能保存数据,是不变类,是线程安全的。
原子性
原子性是指一个操作是不可中断的,要么全部执行成功要么全部执行失败
有序性
Java天然的有序性,如果在本线程内观察,所有的操作都是有序的;如果在一个线程观察另一个线程,所有的操作都是无序的
可见性
可见性是指当一个线程修改了共享变量后,其他线程能够立即得知这个修改。
java.util.concurrent
2、线程 or 进程
1)线程共享内存空间,进程的内存是独立的。
2)同一个进程的线程之间可以直接交流,两个进程想通信,必须通过一个中间代理来实现。
3)创建新进程很简单,创建新进程需要对其父进程进行一个克隆。
4)一个线程可以控制和操作同一进程里的其他线程,但是进程只能操作子进程。
5)改变注线程(如优先权),可能会影响其他线程,改变父进程,不影响子进程。
6)线程是操作系统能够进行运算调度的最小单位(程序执行流的最小单元),进程是线程是操作系统能够进行运算调度的最小单位(程序执行流的最小单元)。
线程、进程都有各自的优势,并不是说那个好哪个坏,需要根据具体场景做具体的适配,Java是典型的多线程并发,PHP多进程并发(最近刚看的)。
3、Java 线程与操作系统线程
操作系统线程模型:(几种古老的模型,但是对于理解现在复杂的操作系统模型提供了很大帮助。ps:以下三点为引用片段,非原创)
1)线程实现在用户空间下
当线程在用户空间下实现时,操作系统对线程的存在一无所知,操作系统只能看到进程,而不能看到线程。所有的线程都是在用户空间实现。在操作系统看来,每一个进程只有一个线程。过去的操作系统大部分是这种实现方式,这种方式的好处之一就是即使操作系统不支持线程,也可以通过库函数来支持线程。
这种模式最致命的缺点也是由于操作系统不知道线程的存在,因此当一个进程中的某一个线程进行系统调用时,比如缺页中断而导致线程阻塞,此时操作系统会阻塞整个进程,即使这个进程中其它线程还在工作。还有一个问题是假如进程中一个线程长时间不释放CPU,因为用户空间并没有时钟中断机制,会导致此进程中的其它线程得不到CPU而持续等待。
2)内核线程就是直接由操作系统内核(Kernel)支持的线程。
这种线程由内核来完成线程切换,内核通过操纵调度器(Scheduler)对线程进行调度,并负责将线程的任务映射到各个处理器上。每个内核线程可以视为内核的一个分身,这样操作系统就有能力同时处理多件事情,支持多线程的内核就叫做多线程内核(Multi-Threads Kernel)。
3)混合实现下,即存在用户线程,也存在轻量级进程。
用户线程还是完全建立在用户空间中,因此用户线程的创建、切换、析构等操作依然廉价,并且可以支持大规模的用户线程并发。而操作系统提供支持的轻量级进程则作为用户线程和内核线程之间的桥梁,这样可以使用内核提供的线程调度功能及处理器映射,并且用户线程的系统调用要通过轻量级进程来完成,大大降低了整个进程被完全阻塞的风险。在这种混合模式中,用户线程与轻量级进程的数量比是不定的,即为N:M的关系。
领取专属 10元无门槛券
私享最新 技术干货