Java 线程 —— 基础篇

来源:互联网 发布:建筑节能分析软件 编辑:程序博客网 时间:2024/05/17 21:06

一、操作系统中线程和进程的概念

现在的操作系统是多任务操作系统。多线程是实现多任务的一种方式。

进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。

线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如Java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。

"同时"执行是人的感觉,在线程之间实际上轮换执行。

 
二、Java中的线程

在Java中,“线程”指两件不同的事情:

1、java.lang.Thread类的一个实例;
2、线程的执行。

 

使用java.lang.Thread类或者java.lang.Runnable接口编写代码来定义、实例化和启动新线程。

一个Thread类实例只是一个对象,像Java中的任何其他对象一样,具有变量和方法,生死于堆上。

Java中,每个线程都有一个调用栈,即使不在程序中创建任何新的线程,线程也在后台运行着。

一个Java应用总是从main()方法开始运行,mian()方法运行在一个线程内,它被称为主线程。

一旦创建一个新的线程,就产生一个新的调用栈。

 

线程总体分两类:用户线程守候线程

当所有用户线程执行完毕的时候,JVM自动关闭。但是守候线程却不独立于JVM,守候线程一般是由操作系统或者用户自己创建的。

 

三、线程的生命周期及五种基本状态

关于Java中线程的生命周期,首先看一下下面这张较为经典的图:


 


上图中基本上囊括了Java中多线程各重要知识点。掌握了上图中的各知识点,Java中的多线程也就基本上掌握了。主要包括:

Java线程具有五种基本状态

新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();

就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就     绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。根据阻塞产生的原因不同,阻塞状态又可以分为三种:

1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;

2.同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

3.其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

 

四、Java 线程的创建和启动

1、定义线程

1)、扩展java.lang.Thread类。

此类中有个run()方法,应该注意其用法:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public void run()  

如果该线程是使用独立的Runnable运行对象构造的,则调用该Runnable对象的run方法;否则,该方法不执行任何操作并返回。

Thread的子类应该重写该方法

 

2)、实现java.lang.Runnable接口。

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. void run()  

使用实现接口Runnable的对象创建一个线程时,启动该线程将导致在独立执行的线程中调用对象的run方法。

方法run的常规协定是,它可能执行任何所需的操作

 

2、实例化线程

1)、如果是扩展java.lang.Thread类的线程,则直接new即可

2)、如果是实现了java.lang.Runnable接口的类,则用Thread的构造方法

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. Thread(Runnable target)   
  2. Thread(Runnable target, String name)   
  3. Thread(ThreadGroup group, Runnable target)   
  4. Thread(ThreadGroup group, Runnable target, String name)   
  5. Thread(ThreadGroup group, Runnable target, String name, long stackSize)   

 

3、启动线程

       在线程的Thread对象上调用start()方法,而不是run()或者别的方法。

 在调用start()方法之前:线程处于新状态中,新状态指有一个Thread对象,但还没有一个真正的线程。在调用start()方法之后:发生了一系列复杂的事情:

启动新的执行线程(具有新的调用栈);

该线程从新状态转移到可运行状态;

当该线程获得机会执行时,其目标run()方法将运行。

      注意:对Java来说,run()方法没有任何特别之处。像main()方法一样,它只是新线程知道调用的方法名称(和签名)。因此,在Runnable上或者Thread上调用run方法是合法的。但并不启动新的线程。

 

4、实例解析

1)继承Thread类,重写该类的run()方法。

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package cn.com.qiang.threaddemo;  
  2.   
  3. public class ThreadTest {  
  4.     public static void main(String[] args) {  
  5.     for(int i = 0; i < 10; i++){  
  6.         System.out.println(Thread.currentThread().getName()+" "+i);  
  7.         if(i == 3){  
  8.             Thread myThread1 = new MyThread();// 创建一个新的线程  myThread1  此线程进入新建状态  
  9.             Thread myThread2 = new MyThread();// 创建一个新的线程  myThread1  此线程进入新建状态  
  10.             myThread1.start();                // 调用start()方法使得线程进入就绪状态  
  11.             myThread2.start();                // 调用start()方法使得线程进入就绪状态  
  12.         }  
  13.     }  
  14. }  
  15. }  
  16.   
  17. class MyThread extends Thread{  
  18.     private int i = 0;  
  19.       
  20.     public void run(){  
  21.         for(i = 0; i < 10; i++){  
  22.             System.out.println(Thread.currentThread().getName()+" "+i);  
  23.         }  
  24.     }  
  25. }  

执行结果如下:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. main 0  
  2. main 1  
  3. main 2  
  4. main 3  
  5. Thread-0 0  
  6. Thread-0 1  
  7. Thread-0 2  
  8. Thread-0 3  
  9. main 4  
  10. main 5  
  11. main 6  
  12. main 7  
  13. main 8  
  14. main 9  
  15. Thread-0 4  
  16. Thread-0 5  
  17. Thread-0 6  
  18. Thread-1 0  
  19. Thread-1 1  
  20. Thread-1 2  
  21. Thread-1 3  
  22. Thread-1 4  
  23. Thread-1 5  
  24. Thread-1 6  
  25. Thread-1 7  
  26. Thread-1 8  
  27. Thread-1 9  
  28. Thread-0 7  
  29. Thread-0 8  
  30. Thread-0 9  

如上所示,继承Thread类,通过重写run()方法定义了一个新的线程类MyThread,其中run()方法的方法体代表了线程需要完成的任务,称之为线程执行体。当创建此线程类对象时一个新的线程得以创建,并进入到线程新建状态。通过调用线程对象引用的start()方法,使得该线程进入到就绪状态,此时此线程并不一定会马上得以执行,这取决于CPU调度时机。

 

2)、实现Runnable接口,并重写该接口的run()方法,该run()方法同样是线程执行体,创建Runnable实现类的实例,并以此实例作为Thread类的target来创建Thread对象,该Thread对象才是真正的线程对象。

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package cn.com.qiang.threaddemo;  
  2.   
  3. public class TreadTest2 {  
  4.       
  5.     public static void main(String[] args) {  
  6.         for(int i = 0; i < 10; i++){  
  7.             System.out.println(Thread.currentThread().getName()+" "+i);  
  8.             if(i == 3){  
  9.                 Runnable myRunnable = new MyRunnable();//创建一个Runnable实现类的对象  
  10.                 Thread thread1 = new Thread(myRunnable);//将myRunnable作为Thread target创建新的线程  
  11.                 Thread thread2 = new Thread(myRunnable);  
  12.                 thread1.start();//调用start()方法使得线程进入就绪状态  
  13.                 thread2.start();  
  14.             }  
  15.         }  
  16.     }  
  17. }  
  18.   
  19. class MyRunnable implements Runnable{  
  20.     private int i = 0;  
  21.       
  22.     public void run(){  
  23.         for(i = 0; i < 10; i++){  
  24.             System.out.println(Thread.currentThread().getName()+" "+i);  
  25.         }  
  26.     }  
  27. }  

执行结果如下:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. main 0  
  2. main 1  
  3. main 2  
  4. main 3  
  5. main 4  
  6. main 5  
  7. Thread-0 0  
  8. Thread-0 1  
  9. Thread-1 0  
  10. main 6  
  11. main 7  
  12. main 8  
  13. main 9  
  14. Thread-0 1  
  15. Thread-0 3  
  16. Thread-1 3  
  17. Thread-1 4  
  18. Thread-1 5  
  19. Thread-1 7  
  20. Thread-1 8  
  21. Thread-1 9  
  22. Thread-0 6  


3)二者之间的关系是什么样的呢,下面看个例子:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package cn.com.qiang.threaddemo;  
  2.   
  3. public class ThreadTest3 {  
  4.     public static void main(String[] args) {  
  5.         for(int i = 0; i < 10; i++){  
  6.             System.out.println(Thread.currentThread().getName()+" "+i);  
  7.             if(i == 3){  
  8.                 Runnable myRunnable2 = new MyRunnable2();  
  9.                 Thread thread = new MyThread2(myRunnable2);  
  10.                 thread.start();  
  11.                   
  12.             }  
  13.         }  
  14.     }  
  15. }  
  16.   
  17. class MyRunnable2 implements Runnable{  
  18.     private int i = 0;  
  19.       
  20.     public void run(){  
  21.         System.out.println("in MyRunnable run!");  
  22.         for(i = 0; i < 10; i++){  
  23.             System.out.println(Thread.currentThread().getName()+" "+i);  
  24.         }  
  25.     }  
  26. }  
  27.   
  28. class MyThread2 extends Thread{  
  29.       
  30.     private int i = 0;  
  31.       
  32.     public MyThread2(Runnable MyRunnable2) {  
  33.         super(MyRunnable2);  
  34.     }  
  35.       
  36.     public void run(){  
  37.         System.out.println("in MyThread run");  
  38.         for(i = 0; i< 10; i++){  
  39.             System.out.println(Thread.currentThread().getName()+" "+i);  
  40.         }  
  41.     }  
  42. }  

执行结果如下:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. main 0  
  2. main 1  
  3. main 2  
  4. main 3  
  5. main 4  
  6. in MyThread run  
  7. main 5  
  8. Thread-0 0  
  9. main 6  
  10. Thread-0 1  
  11. Thread-0 2  
  12. Thread-0 3  
  13. Thread-0 4  
  14. Thread-0 5  
  15. Thread-0 6  
  16. Thread-0 7  
  17. Thread-0 8  
  18. Thread-0 9  
  19. main 7  
  20. main 8  
  21. main 9  

同样的,与实现Runnable接口创建线程方式相似,不同的地方在于

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. Thread thread = new MyThread(myRunnable);  

那么这种方式可以顺利创建出一个新的线程么?答案是肯定的。至于此时的线程执行体到底是MyRunnable接口中的run()方法还是MyThread类中的run()方法呢?通过输出我们知道线程执行体是MyThread类中的run()方法。其实原因很简单,因为Thread类本身也是实现了Runnable接口,而run()方法最先是在Runnable接口中定义的方法。

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public interface Runnable {  
  2.       
  3.      public abstract void run();     
  4. }  

我们看一下Thread类中对Runnable接口中run()方法的实现

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. @Override  
  2. public void run() {  
  3.     if (target != null) {  
  4.         target.run();  
  5.     }  
  6. }  

也就是说,当执行到Thread类中的run()方法时,会首先判断target是否存在,存在则执行target中的run()方法,也就是实现了Runnable接口并重写了run()方法的类中的run()方法。但是上述给到的列子中,由于多态的存在,根本就没有执行到Thread类中的run()方法,而是直接先执行了运行时类型即MyThread类中的run()方法。

 

总结:两种线程创建方式的比较

1、使用Runnable接口

1)可以将CPU,代码和数据分开,形成清晰的模型;

2)还可以从其他类继承;

3)保持程序风格的一致性;

 

2、直接继承 Thread 类

1)不能够再从其他类继承;

2)编写简单,可以直接操纵线程,无需使用Thread.currentThread(). 

0 0
原创粉丝点击