所谓进程就是运行在操做系统的一个任务,进程是计算机任务调度的一个单位,操做系统在启动一个程序的时候,会为其建立一个进程,JVM就是一个进程。进程与进程之间是相互隔离的,每一个进程都有独立的内存空间。java
计算机实现并发的原理是:CPU分时间片,交替执行,宏观并行,微观串行。同理,在进程的基础上分出更小的任务调度单元就是线程,咱们所谓的多线程就是一个进程并发多个线程。数据库
在上面咱们提到,一个进程能够并发出多个线程,而线程就是最小的任务执行单元,具体来讲,一个程序顺序执行的流程就是一个线程,咱们常见的main就是一个线程(主线程)。多线程
想要拥有一个线程,有这样的一些不可或缺的部分,主要有:CPU时间片,数据存储空间,代码。并发
CPU时间片都是有操做系统进行分配的,数据存储空间就是咱们常说的堆空间和栈空间,在线程之间,堆空间是多线程共享的,栈空间是互相独立的,这样作的好处不只在于方便,也减小了不少资源的浪费。代码就不作过多解释了,没有代码搞个毛的多线程。工具
Runnable并非线程对象,而是一个任务对象。那么Runnable和Thread有什么样的关系呢?经过查阅API,咱们发现建立一个线程除了使用Thread的无参构造方法之外有一个有参构造方法是这样 :Thread(Runnable target),经过这个方法会分配一个新的Thread 对象。spa
其中的参数是一个类型为Runnable的target属性。操作系统
Runnable接口最大的做用就是为非Thread子类的类提供了一种实现线程的方式,只须要实现Runnable接口就能够借助Thread建立一个线程;另外一方面,若是只想重写run方法,不想获得其余的Thread的方法,实现Runnable是一个好的选择。线程
线程池code
ExecutorService(线程池 interface)对象
//经过工具类中的方法可以新建一个线程池,用ExecutorService接受 ExecutorService es = Executors.newFixedThreadPool(2);
Callable对象
相似于Runnable(描述任务的interface)。
//建立一个Callable的实现类 Callable<Integer> task1 = new Callable<Integer>(){ public Integer call() throws Exception{ int result = 0; for(int i=2;i<=100;i+=2){ result += i; Thread.sleep; } return result; } } //用Future对象接收fask1的返回值 将任务提交给线程池 Future<Integer> f = es.submit(task1); //经过get方法获取Future中的值 在这个时候主线程主动的调取get 若是分支线程尚未结束,主线程会在这里阻塞 int result = f1.get(); //关闭线程池 es.shutdown();
从以上这段代码咱们能够看到不少不同的地方,首先在Callable对象中是能够抛出异常的,其次有返回值,在这个基础上也就引出了一个新的问题,若是接收该线程的对象?JDK1.5中也给出了解决的方法是Future对象.
启动线程
在这里咱们须要明白,上面两种方式并不会让咱们获得真正的线程,只是获得了线程对象,只有启动线程,才算获得了真正的线程。
经过执行start()方法可以启动一个线程,可是启动线程并非当即执行,成功启动的线程会处于就绪状态,何时执行须要等到拿到时间片以后。
用户线程和守护(Daemon)线程。
守护线程:守护线程会一直运行,直到其余非守护线程都结束的时候,才会结束。有一个典型的守护线程就是:垃圾回收线程,和虚拟机共存亡,直到虚拟机中没有任何线程的时候虚拟机关闭的时候才会终止,简单说就是虚拟机在,它就在,虚拟机亡便亡。
上面咱们提到过,一个线程在启动以后不会立马执行,而是处于就绪状态(Ready),就绪状态就是线程的状态的一种,处于这种状态的线程意味着一切准备就绪, 须要等待系统分配到时间片。为何没有立马运行呢,由于同一时间只有一个线程可以拿到时间片运行,新线程启动的时候让它启动的线程(主线程)正在运行,只有等主线程结束,它才有机会拿到时间片运行。
线程的状态:初始状态(New),就绪状态(Ready),运行状态(Running)(特别说明:在语法的定义中,就绪状态和运行状态是一个状态Runable),等待状态(Waitering),终止状态(Terminated)
RUNNABLE),等待状态(Waitering),终止状态(Terminated)
线程对象被建立出来,即是初始状态,这时候线程对象只是一个普通的对象,并非一个线程。
就绪状态(Ready):执行start方法以后,进入就绪状态,等待被分配到时间片。
运行状态(Running):拿到CPU的线程开始执行。处于运行时间的线程并非永久的持有CPU直到运行结束,极可能没有执行完毕时间片到期,就被收回CPU的使用权了,以后将会处于等待状态。
等待状态分为有限期等待和无限期等待,所谓有限期等待是线程使用sleep方法主动进入休眠,有必定的时间限制,时间到期就从新进入就绪状态,再次等待被CPU选中。
而无限期等待就有些不一样了,无限期并非指永远的等待下去,而是指没有时间限制,可能等待一秒也可能不少秒。至于进入等待的缘由也不尽相同,多是由于CPU时间片到期,也多是由于一个比较耗时的操做(数据库),或者主动的调用join方法。
wait和sleep的区别
wait | sleep |
---|---|
wait()方法是Object类里的方法 | sleep()是Thread类的static(静态)的方法 |
wait()睡眠时,释放对象锁 | sleep()睡眠时,保持对象锁,仍然占有该锁 |
经常使用于线程间通讯 | 经常使用于暂停执行 |
wait和notify/notifyAll是成对出现的, 必须在synchronize块中被调用 |
在我看来,阻塞状态其实是一种比较特殊的等待状态,处于其余等待状态的线程是在等着别的线程执行结束,等着拿CPU的使用权;而处于阻塞状态的线程等待的不只仅是CPU的使用权,主要是锁标记,没有拿到锁标记,即使是CPU有空也没有办法执行。(关于锁见下节:线程同步)
等待和阻塞的区别
等待 | 阻塞 |
---|---|
已经拿到锁对象,或者说不存在拿不到执行不了的状况 | 等待拿到锁对象 |
等待被唤醒 | 等待拿到锁对象 |
已经终止的线程会处于该种状态。
整体上来讲,做为一个线程挺倒霉的,首先,不会知道本身何时被选中;其次在执行过程当中随时可能被打断让出CPU,最后碰到数据库等耗时的操做也要让出CPU去等待,而且就算数据准备好了, 仍然须要等着被挑选。