进程和线程小程序
谈到多线程,就得先讲进程和线程的概念。浏览器
进程服务器
进程能够理解为受操做系统管理的基本运行单元。360浏览器是一个进程、WPS也是一个进程,正在操做系统中运行的".exe"均可以理解为一个进程多线程
线程框架
进程中独立运行的子任务就是一个线程。像QQ.exe运行的时候就有不少子任务在运行,好比聊天线程、好友视频线程、下载文件线程等等。异步
为何要使用多线程性能
若是使用得当,线程能够有效地下降程序的开发和维护等成本,同时提高复杂应用程序的性能。具体说,线程的优点有:spa
一、发挥多处理器的强大能力操作系统
如今,多处理器系统正日益盛行,而且价格不断下降,即时在低端服务器和中断桌面系统中,一般也会采用多个处理器,这种趋势还在进一步加快,由于经过提升时钟频率来提高性能已变得愈来愈困难,处理器生产厂商都开始转而在单个芯片上放置多个处理器核。试想,若是只有单个线程,双核处理器系统上程序只能使用一半的CPU资源,拥有100个处理器的系统上将有99%的资源没法使用。多线程程序则能够同时在多个处理器上执行,若是设计正确,多线程程序能够经过提升处理器资源的利用率来提高系统吞吐率。线程
二、在单处理器系统上得到更高的吞吐率
若是程序是单线程的,那么当程序等待某个同步I/O操做完成时,处理器将处于空闲状态。而在多线程程序中,若是一个线程在等待I/O操做完成,另外一个线程能够继续运行,使得程序能在I/O阻塞期间继续运行。
三、建模的简单性
经过使用线程,能够将复杂而且异步的工做流进一步分解为一组简单而且同步的工做流,每一个工做流在一个单独的线程中运行,并在特定的同步位置进行交互。咱们能够经过一些现有框架来实现上述目标,例如Servlet和RMI,框架负责解决一些细节问题,例如请求管理、线程建立、负载平衡,并在正确的时候将请求分发给正确的应用程序组件。编写Servlet的开发人员不须要了解多少请求在同一时刻要被处理,也不须要了解套接字的输入流或输出流是否被阻塞,当调用Servlet的service方法来响应Web请求时,能够以同步的方式来处理这个请求,就好像它是一个单线程程序。
四、异步事件的简化处理
服务器应用程序在接受多个来自远程客户端的套接字链接请求时,若是为每一个链接都分配其各自的线程而且使用同步I/O,那么就会下降这类程序的开发难度。若是某个应用程序对套接字执行读操做而此时尚未数据到来,那么这个读操做将一直阻塞,直到有数据到达。在单线程应用程序中,这不只意味着在处理请求的过程当中将停顿,并且还意味着在这个线程被阻塞期间,对全部请求的处理都将停顿。为了不这个问题,单线程服务器应用程序必须使用非阻塞I/O,可是这种I/O的复杂性要远远高于同步I/O,而且很容易出错。然而,若是每一个请求都拥有本身的处理线程,那么在处理某个请求时发生的阻塞将不会影响其余请求的处理。
建立线程的方式
建立线程有两种方式:
一、继承Thread,重写父类的run()方法。
public class MyThread00 extends Thread { public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "在运行!"); } } }
public static void main(String[] args) { MyThread00 mt0 = new MyThread00(); mt0.start(); for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "在运行!"); } }
看一下运行结果:
main在运行! Thread-0在运行! main在运行! Thread-0在运行! main在运行! Thread-0在运行! main在运行! Thread-0在运行! Thread-0在运行! main在运行!
看到main线程和Thread-0线程交替运行,效果十分明显。
有可能有些人看不到这么明显的效果,这也很正常。所谓的多线程,指的是两个线程的代码能够同时运行,而没必要一个线程须要等待另外一个线程内的代码执行完才能够运行。对于单核CPU来讲,是没法作到真正的多线程的,每一个时间点上,CPU都会执行特定的代码,因为CPU执行代码时间很快,因此两个线程的代码交替执行看起来像是同时执行的同样。那具体执行某段代码多少时间,就和分时机制系统有关了。分时系统把CPU时间划分为多个时间片,操做系统以时间片为单位片为单位各个线程的代码,越好的CPU分出的时间片越小。因此看不到明显效果也很正常,一个线程打印5句话原本就很快,可能在分出的时间片内就执行完成了。因此,最简单的解决办法就是把for循环的值调大一点就能够了(也能够在for循环里加Thread.sleep方法,这个以后再说)。
二、实现Runnable接口。和继承自Thread类差很少,不过实现Runnable后,仍是要经过一个Thread来启动:
public class MyThread01 implements Runnable { public void run() { for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "在运行!"); } } }
public static void main(String[] args) { MyThread01 mt0 = new MyThread01(); Thread t = new Thread(mt0); t.start(); for (int i = 0; i < 5; i++) { System.out.println(Thread.currentThread().getName() + "在运行!"); } }
效果也十分明显:
main在运行! Thread-0在运行! main在运行! Thread-0在运行! main在运行! Thread-0在运行! main在运行! Thread-0在运行! main在运行! Thread-0在运行!
两种多线程实现方式的对比
看一下Thread类的API:
其实Thread类也是实现的Runnable接口。两种实现方式对比的关键就在于extends和implements的对比,固然是后者好。由于第一,继承只能单继承,实现能够多实现;第二,实现的方式对比继承的方式,也有利于减少程序之间的耦合。
所以,多线程的实现几乎都是使用的Runnable接口的方式。不过,后面的文章,为了简单,就用继承Thread类的方式了。
线程状态
虚拟机中的线程状态有六种,定义在Thread.State中:
一、新建状态NEW
new了可是没有启动的线程的状态。好比"Thread t = new Thread()",t就是一个处于NEW状态的线程
二、可运行状态RUNNABLE
new出来线程,调用start()方法即处于RUNNABLE状态了。处于RUNNABLE状态的线程可能正在Java虚拟机中运行,也可能正在等待处理器的资源,由于一个线程必须得到CPU的资源后,才能够运行其run()方法中的内容,不然排队等待
三、阻塞BLOCKED
若是某一线程正在等待监视器锁,以便进入一个同步的块/方法,那么这个线程的状态就是阻塞BLOCKED
四、等待WAITING
某一线程由于调用不带超时的Object的wait()方法、不带超时的Thread的join()方法、LockSupport的park()方法,就会处于等待WAITING状态
五、超时等待TIMED_WAITING
某一线程由于调用带有指定正等待时间的Object的wait()方法、Thread的join()方法、Thread的sleep()方法、LockSupport的parkNanos()方法、LockSupport的parkUntil()方法,就会处于超时等待TIMED_WAITING状态
六、终止状态TERMINATED
线程调用终止或者run()方法执行结束后,线程即处于终止状态。处于终止状态的线程不具有继续运行的能力