进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。
线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。
一个线程不能独立的存在,它必须是进程的一部分。一个进程一直运行,直到所有的非守候线程都结束运行后才能结束。
一般来说,当运行一个应用程序的时候,就启动了一个进程,当然有些会启动多个进程。启动进程的时候,操作系统会为进程分配资源,其中最主要的资源是内存空间,因为程序是在内存中运行的。
在进程中,有些程序流程块是可以乱序执行的,并且这个代码块可以同时被多次执行。实际上,这样的代码块就是线程体。线程是进程中乱序执行的代码流程。当多个线程同时运行的时候,这样的执行模式成为并发执行。
线程共有下面4种状态:
新建状态(New):新创建了一个线程对象,当你用new创建一个线程时,该线程尚未运行。
就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。
运行状态(Running):就绪状态的线程获取了CPU,执行程序代码。
阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。
阻塞的情况分三种:
a. 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。
b. 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM把该线程放入锁。
c. 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
死亡状态(Dead):
a. 由于run方法的正常退出而自然死亡;
b. 没有捕获到的异常事件终止了run方法的执行,从而导致线程突然死亡
下图反映了线程在不同情况下的状态变化:
线程状态的相关问题:
1.若要确定某个线程当前是否活着,可以使用 isAlive 方法。如果该线程是可运行线程或者被中断线程,那么该方法返回true;如果该线程仍然是个新建线程,或者该线程是个死线程,那么该方法返回false
2、你无法确定一个活线程究竟是处于可运行状态还是被中断状态,也无法确定一个可运行线程是否正处在运行之中。另外,你也无法对尚未成为可运行的线程与已经死掉的线程进行区分。
3、线程必须退出中断状态,并且返回到可运行状态,方法是使用与进入中断状态相反的过程:
a. 如果线程已经处于睡眠状态,就必须经过规定的毫秒数
b. 如果线程正在等待输入或输出操作完成,那么必须等待该操作完成
c. 如果线程调用了wait方法,那么另外一个线程必须调用notifyAll或者notify方法
d. 如果线程正在等待另一个线程拥有的对象锁,那么另一个线程必须放弃该锁的所有权
sleep方法与wait方法的区别:
- sleep方法是静态方法,wait方法是非静态方法。
- sleep方法在时间到后会自己“醒来”,但wait不能,必须由其它线程通过notify(All)方法让它“醒来”。
- sleep方法通常用在不需要等待资源情况下的阻塞,像等待线程、数据库连接的情况一般用wait。
sleep/wait与yeld方法的区别:
调用sleep或wait方法后,线程即进入block状态,而调用yeld方法后,线程进入runnable状态。
wait与join方法的区别:
- wait方法体现了线程之间的互斥关系,而join方法体现了线程之间的同步关系。
- wait方法必须由其它线程来解锁,而join方法不需要,只要被等待线程执行完毕,当前线程自动变为就绪。
- join方法的一个用途就是让子线程在完成业务逻辑执行之前,主线程一直等待直到所有子线程执行完毕。
Java提供了线程类Thread来创建多线程的程序。其实,创建线程与创建普通的类的对象的操作是一样的,而线程就是Thread类或其子类的实例对象。每个Thread对象描述了一个单独的线程。
要产生一个线程,有两种方法:
A继承Thread ,由子类复写run方法
B实现Runnalbe接口,重载Runnalbe接口中的run()方法。
A继承Thread类实现多线程
(1).首先定义一个类ThreadDemo去继承Thread父类,重写父类中的run()方法。在run()方法中加入具体的任务代码或处理逻辑。
(2).直接创建一个ThreadDemo类的对象,也可以利用多态性,变量声明为父类的类型。
(3).调用start方法,线程启动,隐含的调用run()方法。
1 /*继承Thread类实现多线程*/ 2 public class ThreadDemo extends Thread { 3 private int countDown = 4; 4 public void run() { //重写了Thread中的run方法 5 while (countDown - - > 0) { 6 System.out.println(this.getName() + "(" + countDown + ")"); 7 } 8 } 9 public static void main(String[] args) { 10 11 new ThreadDemo ().start(); //用匿名方法声明一个ThreadDemo对象并调用start()方法启动这个线程 12 13 new Thread(t).start(); //创建了Thread对象,注意和第一个不同 14 15 // 由于start方法迅速返回,所以main线程可以执行其他的操作,此时有两个独立的线程在并发运行。所以每次运行的结果可能不相同 16 17 } 18 19 }
其中两次运行结果是:
利用扩展Thread类创建的多个线程,虽然执行的是相同的代码,但彼此相互独立,且各自拥有自己的资源,互不干扰。这就说明每个线程之间是平等的,没有优先级关系,因此都有机会得到CPU的处理。并且线程并不是依次交替执行,而是随机的有的被执行的机会多,有的被执行的机会少,直到最后全部执行完毕
B通过实现Runnable接口线程创建
(1).定义一个类(如TreadDemo1)实现Runnable接口,重写接口中的run()方法(用于封装线程要运行的代码)。在run()方法中加入具体的任务代码或处理逻辑。
(2).创建Runnable接口实现类(TreadDemo1)的对象。
(3).创建一个Thread类的对象,需要封装前面Runnable接口实现类的对象。(要让线程对象明确要运行的run方法所属的对象。 )
(4).调用Thread对象的start()方法,启动线程
/*通过实现Runnable接口线程创建*/
public class TreadDemo1 implements Runnable { private int i = 4; // 在run方法中定义任务 public void run() { while (i - - > 0) { System.out.println("#" + Thread.currentThread().getName() + "("//不能直接使用this + countDown + ")"); } } public static void main(String[] args) { // Runnable中run方法是一个空方法,并不会产生任何线程行为,必须显式地将一个任务附着到线程上 TreadDemo1 t=new TreadDemo1(); //直接创建TreadDemo1对象,并不是创建线程对象。 因为创建对象只能通过new Thread类,或者new Thread类的子类才可以。 为什么要将t传给Thread类的构造函数呢?其实就是为了明确线程要运行的代码run方法。 new Thread(t).start(); //既然没有了Thread类的子类,就只能用Thread类。将t作为Thread类的构造函数的实际参数传入即可完成线程对象和t之间的关联 ,其实就是为了明确线程要运行的代码run方法 new Thread(t).start(); //创建了第二个线程 // 由于start方法迅速返回,所以main线程可以执行其他的操作,此时有两个独立的线程在并发运行。所以每次运行的结果可能不相同 } }
三次运行的结果为:
使用实现Runnable接口方式创建线程可以共享同一个目标对象(TreadDemo1 tt=new TreadDemo1();),实现了多个相同线程处理同一份资源,所以在两个进程中公用了同一个i,使最终的运行结果只有四条记录。
两种方法的比较:
A采用继承Thread类方式:
(1)优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程。
(2)缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类。
B采用实现Runnable接口方式:
(1)优点:线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
(2)缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。