一、什么是线程?什么是进程?
进程是线程的容器,先了解一下进程,区别学习下线程
》进程的描述:
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位进程由程序、数据和进程控制块三部分组成。
程序是指令、数据及其组织形式的描述,进程是程序的实体。所有与该进程有关的资源,都被记录在进程控制块PCB中,以表示该进程拥有这些资源或正在使用它们。
进程也是抢占处理机的调度单位,它拥有一个完整的虚拟地址空间。
》线程的描述:
线程是程序执行流的最小单位,有时被称为轻量级进程。线程是进程中的一个实体,是被系统独立调度和分派的基本单位。
一个标准的线程由线程ID,当亲指令指针,寄存器集合和堆栈组成。它属于某一个进程,并与进程内的其他线程一起共享进程的资源。
一个线程可以创建和撤销另一个线程,同一进程中的多个线程之间可以并发的执行,同一进程内的不同线程共享同一地址空间。。
二、运行状态
》进程的状态:
1)就绪状态(Ready)
进程已获得除处理器外的所需资源,等待分配处理器资源;只要分配了处理器进程就可执行。就绪进程可以按多个优先级来划分队列。例如,当一个进程由于时间片用完而进入就绪状态时,排入低优先级队列;当进程由I/O操作完成而进入就绪状态时,排入高优先级队列。
2)运行状态(Running)
进程占用处理器资源;处于此状态的进程的数目小于等于处理器的数目。在没有其他进程可以执行时(如所有进程都在阻塞状态),通常会自动执行系统的空闲进程。
3)阻塞状态(Blocked)
由于进程等待某种条件(如I/O操作或进程同步),在条件满足之前无法继续执行。该事件发生前即使把处理器资源分配给该进程,也无法运行。
》线程的状态:
1)新线程态(New Thread)
产生一个Thread对象就生成一个新线程。当线程处于"新线程"状态时,仅仅是一个空线程对象,它还没有分配到系统资源。因此只能启动或终止它。任何其他操作都会引发异常。例如,一个线程调用了new方法之后,并在调用start方法之前的处于新线程状态,可以调用start和stop方法。
2)可运行态(Runnable)
start方法产生运行线程所必须的资源,调度线程执行,并且调用线程的run方法。在这时线程的生命状态与周期线程的生命状态与周期线程处于可运行态。该状态不称为运行态是因为这时的线程并不总是一直占用处理机。特别是对于只有一个处理机的PC而言,任何时刻只能有一个处于可运行态的线程占用处理机。注意,如果线程处于Runnable状态,它也有可能不在运行,这是因为还有优先级和调度问题。
3)阻塞态
当以下事件发生时,线程进入非运行态。
①suspend()方法被调用;
②sleep()方法被调用;
③线程使用wait()来等待条件变量;
④线程处于I/O请求的等待。
4)死亡态
当run方法返回,或别的线程调用stop方法,线程进入死亡态。
三、常见疑问
1、什么是死锁?
死锁的规范定义:集合中的每一个进程都在等待只能由本集合中的其他进程才能引发的事件,那么该组进程是死锁的。
死锁是两个或两个以上的线程被无限的阻塞,线程之间相互等待所需资源。这种情况可能发生在当两个线程尝试获取其它资源的锁,而每个线程又陷入无限等待其它资源锁的释放,除非一个用户进程被终止。
死锁的发生必须具备以下四个必要条件。
1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
典型例子:系统中只有一台CD-ROM驱动器和一台打印机,某一个进程占有了CD-ROM驱动器,又申请打印机;另一进程占有了打印机,还申请CD-ROM。结果,两个进程都被阻塞,永远也不能自行解除。
2、什么是活锁?
活锁是一种特殊的饿死(starvation)(一个线程长时间得不到需要的资源而不能执行的现象)。
任务或者执行者没有被阻塞,数据资源释放时间不确定,导致某些事务长时间等待;一直重复尝试,失败,尝试,失败,得不到封锁的机会。
活锁应该是一系列进程在轮询地等待某个不可能为真的条件为真。活锁的时候进程是不会blocked,这会导致耗尽CPU资源。
典型例子: 两个人在窄路相遇,同时向一个方向避让,然后又向另一个方向避让,如此反复。
3、什么是线程同步?
在多线程程序下,同步能控制对共享资源的访问。如果没有同步,当一个Java线程在修改一个共享变量时,另外一个线程正在使用或者更新同一个变量,这样容易导致程序出现错误的结果。
测试例子:
public class Test { public static void main(String[] args) { final Outputter output = new Outputter(); new Thread() { public void run() { output.output2("hello"); }; }.start(); new Thread() { public void run() { output.output2("world"); }; }.start(); } } class Outputter { public synchronized void output(String name) { // TODO 为了保证对name的输出不是一个原子操作,这里逐个输出name的每个字符 for(int i = 0; i < name.length(); i++) { System.out.print(name.charAt(i)); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } public void output2(String name) { // TODO 为了保证对name的输出不是一个原子操作,这里逐个输出name的每个字符 for(int i = 0; i < name.length(); i++) { System.out.print(name.charAt(i)); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } }
3、Thread.start()与Thread.run()有什么区别?
Thread.start()方法(由本地方法实现,需要显示地被调用)启动线程,使之进入就绪状态,当cpu分配时间该线程时,由JVM调度执行run()方法。
4、sleep()、suspend()和wait()之间有什么区别?
Thread.sleep()使当前线程在指定的时间处于“非运行”(Not Runnable)状态。线程一直持有对象的监视器。比如一个线程当前在一个同步块或同步方法中,其它线程不能进入该块或方法中。如果另一线程调用了 interrupt()方法,它将唤醒那个“睡眠的”线程。
注意:sleep()是一个静态方法。这意味着只对当前线程有效,一个常见的错误是调用t.sleep(),(这里的t是一个不同于当前线程的线程)。即便是执行t.sleep(),也是当前线程进入睡眠,而不是t线程。t.suspend()是过时的方法,使用suspend()导致线程进入停滞状态,该线程会一直持有对象的监视器,suspend()容易引起死锁问题。
object.wait()使当前线程出于“不可运行”状态,和sleep()不同的是wait是object的方法而不是thread。A线程执行obj.wait()方法后,它将释放其所占有的对象锁,A线程进入阻塞状态,同时A也就不具有了获得obj对象所的权力,这样其它线程就可以拿到这把锁了。obj.notifyAll()可以唤醒因obj对象而阻塞的所有线程,并允许它们有获得对象所的权力,obj.notify()是唤醒因obj对象而阻塞的任意一个线程。