hansheng1988

进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在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方法的区别:

  1. sleep方法是静态方法,wait方法是非静态方法。
  2. sleep方法在时间到后会自己“醒来”,但wait不能,必须由其它线程通过notify(All)方法让它“醒来”。
  3. sleep方法通常用在不需要等待资源情况下的阻塞,像等待线程、数据库连接的情况一般用wait。

sleep/wait与yeld方法的区别:

调用sleep或wait方法后,线程即进入block状态,而调用yeld方法后,线程进入runnable状态。

wait与join方法的区别:

  1. wait方法体现了线程之间的互斥关系,而join方法体现了线程之间的同步关系。
  2. wait方法必须由其它线程来解锁,而join方法不需要,只要被等待线程执行完毕,当前线程自动变为就绪。
  3. 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()方法。

 

分类:

技术点:

相关文章: