进程和线程区别
进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问隶属进程的资源。
线程是独立调度的基本单位,在同一进程中,线程的切换不会引起进程切换,从一个进程中的线程切换到另一个进程中的线程时,会引起进程切换。
由于创建或撤销进程时,系统都要为之分配或回收资源,如内存空间、I/O 设备等,所付出的开销远大于创建或撤销线程时的开销。类似地,在进行进程切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置,而线程切换时只需保存和设置少量寄存器内容,开销很小。
线程间可以通过直接读写同一进程中的数据进行通信,但是进程通信需要借助 IPC。
进程和线程的状态
1)进程:
- 就绪状态(ready):等待被调度
- 运行状态(running)
- 阻塞状态(waiting):等待资源
应该注意以下内容:
- 只有就绪态和运行态可以相互转换,其它的都是单向转换。就绪状态的进程通过调度算法从而获得 CPU 时间,转为运行状态;而运行状态的进程,在分配给它的 CPU 时间片用完之后就会转为就绪状态,等待下一次调度。
- 阻塞状态是缺少需要的资源从而由运行状态转换而来,但是该资源不包括 CPU 时间,缺少 CPU 时间会从运行态转换为就绪态。
2)Java线程状态:
一共有6种,分别是新建、运行、阻塞、等待、超时等待、终止。即new、runnable、blocked、waiting、timed-waiting、terminated。
- New:线程刚被创建时,未调用start方法,还未被纳入线程调度,此时为新建状态。
- Runnable:Java中runnable与running统称runnable,此时调用了start方法(runnable),获取到cpu时间片后即可运行(running)。线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。
- blocked:阻塞状态,阻塞于锁。是线程阻塞在进入synchronized关键字修饰的方法或代码块(获取锁)时的状态。
- waiting:此状态的线程需要其他线程的操作,例如通知或中断。处于这种状态的线程不会被分配CPU执行时间,它们要等待被显式地唤醒,否则会处于无限期等待的状态。
- timed-waiting:相比于waiting,该状态可以自定义时间后自行返回。无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。
- terminated:表示线程已经执行完毕。当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
进程间的通信方式
一、管道,通常指匿名管道,是 UNIX 系统IPC最古老的形式。
-
它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。
-
它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。
-
它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。
二、FIFO,也称为命名管道,它是一种文件类型。
-
FIFO可以在无关的进程之间交换数据,与无名管道不同。
-
FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
三、消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。
-
消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
-
消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。
消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。
四、信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。
-
信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。
-
信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。
-
每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。
-
支持信号量组。
五、共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。
-
共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
-
因为多个进程可以同时操作,所以需要进行同步。
-
信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。
线程间的通信方式
线程间通信的模型有两种:共享内存和消息传递
方式一:使用 volatile 关键字修饰共享变量
方式二:使用Object类的wait() 和 notify() 方法
方式三:使用JUC工具类 CountDownLatch
方式四:使用 ReentrantLock 结合 Condition
方式五:基本LockSupport实现线程间的阻塞和唤醒
具体参考:https://blog.csdn.net/jisuanji12306/article/details/86363390
Java内存模型JMM
在并发编程中,我们需要处理两个关键问题:线程之间如何通信及线程之间如何同步,而Java内存模型描述了在多线程代码中哪些行为是合法的,以及线程如何通过内存进行交互。
出现内存模型的原因是CPU的频率无法再提高了,计算机开始面向多核化发展。为了充分利用多核带来的性能提升,便引入的并发编程,但是并发编程会有很多问题,比如处理共享变量的可见性问题和顺序性问题,那么就有了内存模型来帮助解决这个问题。
Java内存模型中我认为最重要的就是happens-before规则,规定了“哪些情况下后面的代码对前面的代码是可见的”:
1、单线程中,前面的代码应该happens before于后面的代码。(这个很好理解,先来后到)
2、对同一个监视器锁的解锁应该happens before于后面的加锁。(一个监视器只能同时被一个线程持有,前一个线程解锁,后面的线程才能加锁,这也是synchronized遵守的规则之一)
3、volatile字段的写入应该happens before于后面对同一个volatile字段的读取。
4、主线程中启动子线程,子线程能看到启动前主线程中的所有操作。
5、主线程中启动子线程,然后子线程调用join方法,主线程等待子线程执行结束,执行结束返回后,主线程对看到子线程的所有操作。
提供了synchronized、volatile、final三个关键字来解决可见性、有序性问题。
单核多线程问题
单核CPU的计算机上,多线程存在安全问题吗?
单核cpu上多线程仍然会存在线程安全问题,因为单核cpu仍然存在线程切换,在执行非原子操作的时候,仍然存在线程问题。
如果操作不是原子操作,你无法控制cpu在什么时机切换线程
具体参考:https://blog.csdn.net/weixin_37968613/article/details/105510239
进程和线程的调度算法
进程调度
1、先来先服务FCFS
在进程调度中采用 FCFS 算法时,则每次调度是从就绪队列中选择一个最先进入该队列的进程,为之分配cpu,使之投入运行。该进程一直运行到完成或发生某事件而阻塞后才放弃处理机,特点是:算法比较简单,可以实现基本上的公平。
2、时间片轮转法
系统将所有的就绪进程按先来先服务的原则排成一个队列,每次调度时,把 CPU 分配给队首进程,并令其执行一个时间片。时间片的大小从几 ms 到几百 ms。当执行的时间片用完时,由一个计时器发出时钟中断请求,调度程序便据此信号来停止该进程的执行,并将它送往就绪队列的末尾;然后,再把处理机分配给就绪队列中新的队首进程,同时也让它执行一个时间片。这样就可以保证就绪队列中的所有进程在一给定的时间内均能获得一时间片的处理机执行时间。
3、最短进程优先
最短进程优先是一个非抢占策略,他的原则是下一次选择预计处理时间最短的进程,因此短进程将会越过长作业,跳至队列头。该算法即可用于作业调度,也可用于进程调度。但是他对长作业不利,不能保证紧迫性作业(进程)被及时处理,作业的长短只是被估算出来的。
4、最短剩余时间优先
最短剩余时间是针对最短进程优先增加了抢占机制的版本。在这种情况下,进程调度总是选择预期剩余时间最短的进程。当一个进程加入到就绪队列时,他可能比当前运行的进程具有更短的剩余时间,因此只要新进程就绪,调度程序就能可能抢占当前正在运行的进程。像最短进程优先一样,调度程序正在执行选择函数是必须有关于处理时间的估计,并且存在长进程饥饿的危险。
5、最高响应比优先
根据比率:R=(w+s)/s (R为响应比,w为等待时间,s为预计要求的服务时间)
(1) 如果作业的等待时间相同,则要求服务的时间愈短,其优先权愈高,因而该算法有利于短作业。
(2) 当要求服务的时间相同时,作业的优先权决定于其等待时间,等待时间愈长,其优先权愈高,因而它实现的是先来先服务。
(3) 对于长作业,作业的优先级可以随等待时间的增加而提高,当其等待时间足够长时,其优先级便可升到很高,从而也可获得处理机。简言之,该算法既照顾了短作业,又考虑了作业到达的先后次序,不会使长作业长期得不到服务。因此,该算法实现了一种较好的折衷。当然,在利用该算法时,每要进行调度之前,都须先做响应比的计算,这会增加系统开销。
6、反馈法
如果没有关于进程相对长度的任何信息,则最短进程优先,最短剩余时间、最高响应优先比都不能使用。另一种导致偏向短作业的方法是处罚运行时间较长的作业,换句话说,如果不能获得剩余的执行时间,那就关注已执行了的时间。
方法为:调度基于被抢占原则(按时间片)并使用动态优先级机制。当一个进程第一次进入系统中时,他被放置在一个优先级队列中,当第一次被抢占后并返回就绪状态时,它被放置在下一个低优先级队列中,在随后的时间里,每当被抢占时,他被降级到下一个低优先级队列中。一个短进程很快被执行完,不会在就绪队列中降很多级,一个长进程会逐渐降级。因此先到的进程和短进程优先于长进程和老进程。在每个队列中,除了优先级在最低的队列中之外,都是用简单的先来先去服务机制,一旦一个进程处于优先级最低的队列中,它就不可能在降级,但会重复的返回该队列,直到运行结束。因此,该队列课按照轮转方式调度。
7、多级反馈队列调度算法
多级反馈队列算法,不必事先知道各种进程所需要执行的时间,他是当前被公认的一种较好的进程调度算法。其实施过程如下:
1)设置多个就绪队列,并为各个队列赋予不同的优先级。在优先权越高的队列中,为每个进程所规定的执行时间片就越小。
2)当一个新进程进入内存后,首先放入第一队列的末尾,按照先来先去原则排队等候调度。如果他能在一个时间片中完成,便可撤离;如果未完成,就转入第二队列的末尾,同样等待调度.....如此下去,当一个长作业(进程)从第一队列依次将到第n队列(最后队列)后,便按第n队列时间片轮转运行。
3)仅当第一队列空闲的时候,调度程序才调度第二队列中的进程运行;仅当第1到(i-1)队列空时,才会调度第i队列中的进程运行,并执行相应的时间片轮转。
4)如果处理机正在处理第i队列中某进程,又有新进程进入优先权较高的队列,则此新队列抢占正在运行的处理机,并把正在运行的进程放在第i队列的队尾。
线程调度
抢占式调度
抢占式调度指的是每条线程执行的时间,线程的切换都由系统控制。系统控制指的是在系统某种机制下,可能每条线程都分同样的执行时间片,也可能是某些线程执行的时间片较长,甚至某些线程得不到执行的时间片。在这种机制下,一个线程的堵塞不会导致整个进程堵塞。
协同式调度
协同式调度指某一线程执行完后主动通知系统切换到另一线程上执行。线程的执行时间由线程本身控制,线程切换可以预知,不存在多线程同步问题,但它有一个致命弱点;如果一个线程编写有问题,运行到一半就一直堵塞,那么可能导致整个系统崩溃。