线程和进程各自有什么区别和优劣呢?
-
进程是资源分配的最小单位,线程是程序执行的最小单位。
-
进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
-
线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。
-
但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。
进程的全局数据,进程的地址空间等等,这些都属于进程
线程也有自己的资源,比如栈,私有数据
进程切换比线程切换开销大:
因为进程切换时要切页表,而且往往伴随着页调度,因为进程的数据段代码段要换出去,以便把将要执行的进程的内容换进来。本来进程的内容就是线程的超集。
而线程只需要保存线程的上下文(相关寄存器状态和栈的信息)就好了,动作很小
线程开的越多越好吗?
不是,线程多了可以提高程序并行执行的速度,但是并不是越多越好,其中,每个线程都要占用内存,多线程就意味着更多的内存资源被占用,其二,从微观上讲,一个cpu不是同时执行两个线程的,他是轮流执行的,所以线程太多,cpu必须不断的在各个线程间快回更换执行,线程间的切换无意间消耗了许多时间,所以cpu有效利用率反而是下降的
僵尸进程和孤儿进程
首先,正常情况下,子进程是通过父进程创建的,子进程在创建新的进程。子进程的结束和父进程的运行是一个异步过程。 当一个子进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。
僵尸进程:一个进程使用fork创建子进程,子进程退出,但是父进程并没有调用wait或waitpid获取子进程的状态信息,那么子 进程的进程描述符仍然保存在系统中(会占用进程号之类,还有占用的内存)
孤儿进程:一个父进程退出,但它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。孤儿进程将被init进程(进程 号为1)所收养,并由init进程对它们完成状态收集工作。
补充:
fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;
问题和危害:
1. 子进程终止时候,如果父进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是 系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程. 此即 为僵尸进程的危害,应当避免。
2. 孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了init进程身上,init进程会循环地wait()它的已经退出的子进程
因此孤儿进程并不会有什么危害。
解决:
僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,应该把产生大量僵死进程的那个元凶枪毙掉(也就是通过kill发送SIGTERM或者SIGKILL信号啦)。枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源
进程间通信方式
- 管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,只能在父子进程中使用。
- 有名管道 (namedpipe) : 有名管道也是半双工的通信方式,但去除了管道只能在父子进程中使用的限制。
- 信号量(semophore ) :信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
- 消息队列( messagequeue ) :
消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。 - 共享内存(shared memory ):共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
- 套接字(socket ) : 套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。
线程间通信方式
-
synchronized同步
- 这种方式,本质上就是 “共享内存” 式的通信。多个线程需要访问同一个共享变量,谁拿到了锁(获得了访问权限),谁就可以执行。
-
while轮询的方式
- 在这种方式下,ThreadA 不断地改变条件,ThreadB 不停地通过 while 语句检测这个条件
(list.size()==5)
是否成立 ,从而实现了线程间的通信。但是这种方式会浪费 CPU 资源。 - 之所以说它浪费资源,是因为 JVM 调度器将 CPU 交给 ThreadB 执行时,它没做啥 “有用” 的工作,只是在不断地测试某个条件是否成立。
- 就类似于现实生活中,某个人一直看着手机屏幕是否有电话来了,而不是:在干别的事情,当有电话来时,响铃通知TA电话来了。
- 在这种方式下,ThreadA 不断地改变条件,ThreadB 不停地通过 while 语句检测这个条件
-
wait/notify机制
-
当条件未满足时,ThreadA 调用 wait() 放弃 CPU,并进入阻塞状态。(不像 while 轮询那样占用 CPU)
当条件满足时,ThreadB 调用 notify() 通知线程 A,所谓通知线程 A,就是唤醒线程 A,并让它进入可运行状态。
-
-
管道通信
- java.io.PipedInputStream 和 java.io.PipedOutputStream进行通信