34、进程与线程的区别
(1)进程概念
进程是表示资源分配的基本单位。 例如,用户运行自己的程序,系统就创建一个进程,并为它分配资源,包括各种表格、内存空间、磁盘空间、I/O设备等。然后,把该进程放入进程的就绪队列。进程调度程序选中它,为它分配CPU以及其它有关资源,该进程才真正运行。所以,进程是系统中的并发执行的单位。
在微内核系统(Mac、Windows NT等)中,真正调度运行的基本单位是线程。因此,实现并发功能的单位是线程。
(2)线程概念
线程是进程中执行运算的最小单位,亦即执行处理机调度的基本单位。 如果把进程理解为在逻辑上操作系统所完成的任务,那么线程表示完成该任务的许多可能的子任务之一。例如,假设用户启动了一个窗口中的数据库应用程序,操作系统就将对数据库的调用表示为一个进程。假设用户要从数据库中产生一份工资单报表,并传到一个文件中,这是一个子任务;在产生工资单报表的过程中,用户又可以输人数据库查询请求,这又是一个子任务。这样,操作系统则把每一个请求――工资单报表和新输人的数据查询表示为数据库进程中的独立的线程。线程可以在处理器上独立调度执行,这样,在多处理器环境下就允许几个线程各自在单独处理器上进行。操作系统提供线程就是为了方便而有效地实现这种并发性。
(3)线程与进程的比较
1、调度:在传统的操作系统中,拥有资源的基本单位和独立调度、分派的基本单位都是进程。而在引入线程的操作系统中,则把线程作为调度和分派的基本单位。而把进程作为资源拥有的基本单位,使传统进程的两个属性分开,线程便能轻装运行,从而可显著地提高系统的并发程度。在同一进程中,线程的切换不会引起进程的切换,在由一个进程中的线程切换到另一个进程中的线程时,将会引起进程的切换。
2、并发性:在引入线程的操作系统中,不仅进程之间可以并发执行,而且在一个进程中的多个线程之间,亦可并发执行,因而使操作系统具有更好的并发性,从而能更有效地使用系统资源和提高系统吞吐量。例如,在一个未引入线程的单CPU操作系统中,若仅设置一个文件服务进程,当它由于某种原因而被阻塞时,便没有其它的文件服务进程来提供服务。在引入了线程的操作系统中,可以在一个文件服务进程中,设置多个服务线程,当第一个线程等待时,文件服务进程中的第二个线程可以继续运行;当第二个线程阻塞时,第三个线程可以继续执行,从而显著地提高了文件服务的质量以及系统吞吐量。
3、拥有资源:不论是传统的操作系统,还是设有线程的操作系统,进程都是拥有资源的一个独立单位,它可以拥有自己的资源。一般地说,线程自己不拥有系统资源(也有一点必不可少的资源),但它可以访问其隶属进程的资源。亦即,一个进程的代码段、数据段以及系统资源,如已打开的文件、I/O设备等,可供同一进程的其它所有线程共享。
4、系统开销:由于在创建或撤消进程时,系统都要为之分配或回收资源,如内存空间、IO设备等。因此,操作系统所付出的开销将显著地大于在创建或撤消线程时的开销。类似地,在进行进程切换时,涉及到整个当前进程CPU环境的保存以及新被调度运行的进程的CPU环境的设置。而线程切换只须保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作。可见,进程切换的开销也远大于线程切换的开销。此外,由于同一进程中的多个线程具有相同的地址空间,致使它们之间的同步和通信的实现,也变得比较容易。
1、进程让操作系统的并发成为可能,而线程让进程内部并发成为可能。
2、进程是操作系统进行资源分配的基本单位,而线程是操作系统进行调度的基本单位。
(4)、并发与并行
并发的关键是有处理多个任务的能力,不一定要同时,并行的关键是有同时处理多个任务的能力(可能有多个CPU)。并发和并行都可以是很多个线程,就看这些线程能不能同时被(多个)CPU执行,如果可以,则是并行,而并发是多个线程被(一个)CPU轮流执行。
35、进程间的通信方式(IPC)
1、管道pipe:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
2、命名管道FIFO:有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
3、消息队列MessageQueue:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
4、共享内存SharedMemory:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
5、信号量Semaphore:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
6、套接字Socket:套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
7、信号signal:信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
35.1、Java线程间通信方式
1、Object类中的wait()/notify()/notifyAll()方法。
2、使用Condition接口
Condition 是被绑定到 Lock 上的,要创建一个 Lock 的 Condition 对象必须用 newCondition()方法。在一个 Lock 对象里面可以创建多个Condition 对象,线程可以注册在指定的 Condition 对象中,从而可以有选择性地进行线程通知,在线程调度上更加灵活。
在 Condition 中,用 await()替换 wait(),用 signal()替换 notify(),用 signalAll()替换 notifyAll(),传统线程的通信方式,Condition 都可以实现。调用 Condition 对象中的方法时,需要被包含在 lock()和 unlock()之间。
3、管道实现线程间的通信。
实现方式:一个线程发送数据到输出管道流,另一个线程从输入管道流中读取数据。
基本流程:
1)、创建管道输出流 PipedOutputStream pos 和管道输入流PipedInputStream pis。
2)、将 pos 和 pis 连接,pos.connect(pis)。
3)、将 pos 赋给信息输入信息的线程,pis 赋给获取信息的线程,就可以实现线程间的通讯了。
管道流通信的缺点:
1)、管道流只能在两个线程之间传递数据。
2)、管道流只能实现单向发送,如果要两个线程之间互通讯,则需要两个管道流。
4、使用volatile关键字。volatile关键字能够保证线程对共享变量的修改对其他线程可见,但它不保证操作的原子性。
36、线程的生命周期(状态)
在 Java 当中,线程通常都有五种状态,新建(new)、就绪(runnable)、运行(running)、阻塞(blocked)和死亡(dead)。
第一是新建(new)状态。在生成线程对象,并没有调用该对象的 start 方法,这是线程处于新建状态。
第二是就绪(runnable)状态。当调用了线程对象的 start 方法之后,该线程就进入了就绪状态,但是此时线程调度程序还没有把该线程设置为当前线程,此时处于就绪状态。在线程运行之后,从等待或者睡眠中回来之后,也会处于就绪状态。
第三是运行(running)状态。线程调度程序将处于就绪状态的线程设置为当前线程,此时线程就进入了运行状态,开始运行 run 函数当中的代码。
第四是阻塞(blocked) 、timed_waiting、waiting状态。线程正在运行的时候,被暂停,通常是为了等待某个事件的发生(比如说某项资源就绪)之后再继续运行。sleep,wait 等方法都可以导致线程阻塞。
第五是死亡(dead)状态。如果一个线程的 run 方法执行结束或者异常中断后,该线程就会死亡。对于已经死亡的线程,无法再使用 start 方法令其进入就绪。
37、创建线程的几种方式
1、继承 Thread类创建线程类
(1)定义 Thread 类的子类,并重写该类的 run 方法,该 run 方法的方法体就代表了线程要完成的任务。因此把 run()方法称为执行体。
(2)创建 Thread 子类的实例,即创建了线程对象。
(3)调用线程对象的 start()方法来启动该线程。
2、通过 Runnable 接口创建线程类
(1)定义 Runnable 接口的实现类,并重写该接口的 run()方法,该 run()方法的方法体同样是该线程的线程执行体。
(2)创建 Runnable 实现类的实例,并以此实例作为 Thread 的 target来创建 Thread 对象,该 Thread 对象才是真正的线程对象。
(3)调用线程对象的 start()方法来启动该线程。
3、通过 Callable 和 Future 创建线程
(1)创建 Callable 接口的实现类,并实现 call()方法,该 call()方法将作为线程执行体,并且有返回值。
(2)创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
(3)使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
(4)调用 FutureTask 对象的 get()方法来获得子线程执行结束后的返回值。
4、通过线程池创建线程
利用线程池不用 new 就可以创建线程,线程可复用,利用 Executors 创建线程池。
38、Java中用户线程和守护线程的区别
当我们在 Java 程序中创建一个线程,它就被称为用户线程。将一个用户线程设置为守护线程的方法就是在调用 start() 方法之前,调用对象的setDamon(true)方法。一个守护线程是在后台执行并且不会阻止 JVM 终止的线程,守护线程的作用是为其他线程的运行提供便利服务。当没有用户线程在运行的时候,JVM关闭程序并且退出。一个守护线程创建的子线程依然是守护线程。
守护线程的一个典型例子就是垃圾回收器。