手记

Java中创建线程的四种方法及其相关

Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。Java可以用四种方式来创建线程,如下:
1)继承Thread类创建线程;
2)实现Runnable接口创建线程;
3)实现Callable接口通过FutureTask包装器来创建Thread线程;
4)使用ExecutorService、Callable、Future实现有返回结果的线程。

其中前两种方式线程执行完后都没有返回值,后两种是带返回值的。

------------------------继承Thread类创建线程---------------------
通过继承Thread类来创建并启动多线程的一般步骤如下:
1】定义Thread类的子类,并重写该类的run()方法,该方法的方法体就是线程需要完成的任务,run()方法也称为线程执行体;
2】创建Thread子类的实例,也就是创建了线程对象;
3】启动线程,即调用线程的start()方法。
代码实例

public class MyThread extends Thread{//继承Thread类
  public void run(){ 
  //重写run方法
  }
}
public class Main {
  public static void main(String[] args){
    new MyThread().start();//创建并启动线程
  }
}

------------------------实现Runnable接口创建线程---------------------
通过实现Runnable接口创建并启动线程一般步骤如下:
1】定义Runnable接口的实现类,一样要重写run()方法,这个run()方法和Thread中的run()方法一样是线程的执行体;
2】创建Runnable实现类的实例,并用这个实例作为Thread的target来创建Thread对象,这个Thread对象才是真正的线程对象;
3】第三步依然是通过调用线程对象的start()方法来启动线程。
代码实例:

public class MyThread implements Runnable {//实现Runnable接口
  public void run(){
  //重写run方法
  }
}
public class Main {
  public static void main(String[] args){
    //创建并启动线程
    MyThread myThread=new MyThread();
    Thread thread=new Thread(myThread);
    thread().start();
    //或者new Thread(new MyThread()).start();
  }
}

------------------------使用Callable和Future创建线程---------------------
和Runnable接口不一样,Callable接口提供了一个call() 方法作为线程执行体,call() 方法比run() 方法功能要强大:

  • call() 方法可以有返回值;
  • call() 方法可以声明抛出异常。

Java5提供了Future接口来代表Callable接口里call() 方法的返回值,并且为Future接口提供了一个实现类FutureTask,这个实现类既实现了Future接口,还实现了Runnable接口,因此可以作为Thread类的target。在Future接口里定义了几个公共方法来控制它关联的Callable任务。

  • boolean cancel(boolean mayInterruptIfRunning):视图取消该Future里面关联的Callable任务;
  • get():返回Callable里call() 方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值;
  • get(long timeout,TimeUnit unit):返回Callable里call() 方法的返回值,最多阻塞timeout时间,经过指定时间没有返回抛出TimeoutException;
  • boolean isDone():若Callable任务完成,返回True;
  • boolean isCancelled():如果在Callable任务正常完成前被取消,返回True。

介绍了相关的概念之后,创建并启动有返回值的线程的步骤如下:
1】创建Callable接口的实现类,并实现call() 方法,然后创建该实现类的实例(从Java8开始可以直接使用Lambda表达式创建Callable对象);
2】使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call() 方法的返回值;
3】使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口);
4】调用FutureTask对象的get() 方法来获得子线程执行结束后的返回值。

代码实例:

public class Main {
  public static void main(String[] args){
   MyThread3 th=new MyThread3();
   //使用Lambda表达式创建Callable对象
    //使用FutureTask类来包装Callable对象
   FutureTask<Integer> future=new FutureTask<Integer>(
    (Callable<Integer>)()->{
      return 5;
    }
    );
   new Thread(task,"有返回值的线程").start();
   //实质上还是以Callable对象来创建并启动线程
    try{
    System.out.println("子线程的返回值:"+future.get());
    //get()方法会阻塞,直到子线程执行结束才返回
    }catch(Exception e){
    ex.printStackTrace();
   }
  }
}

----------使用ExecutorService、Callable、Future实现有返回结果的线程--------
ExecutorService、Callable、Future三个接口实际上都是属于Executor框架。返回结果的线程是在JDK1.5中引入的新特征,有了这种特征就不需要再为了得到返回值而大费周折了。而且自己实现了也可能漏洞百出。
可返回值的任务必须实现Callable接口。类似的,无返回值的任务必须实现Runnable接口。
执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了。
注意:get方法是阻塞的,即:线程无返回结果,get方法会一直等待。
再结合线程池接口ExecutorService就可以实现传说中有返回结果的多线程了。
下面提供了一个完整的有返回结果的多线程测试例子,在JDK1.5下验证过没问题可以直接使用。代码如下:

import java.util.concurrent.*;  
import java.util.Date;  
import java.util.List;  
import java.util.ArrayList;  
/** 
* 有返回值的线程 
*/  
@SuppressWarnings("unchecked")  
public class Test {  
public static void main(String[] args) throws ExecutionException,  
    InterruptedException {  
   System.out.println("----程序开始运行----");  
   Date date1 = new Date();  
   int taskSize = 5;  
   // 创建一个线程池  
   ExecutorService pool = Executors.newFixedThreadPool(taskSize);  
   // 创建多个有返回值的任务  
   List<Future> list = new ArrayList<Future>();  
   for (int i = 0; i < taskSize; i++) {  
    Callable c = new MyCallable(i + " ");  
    // 执行任务并获取Future对象  
    Future f = pool.submit(c);  
    // System.out.println(">>>" + f.get().toString());  
    list.add(f);  
   }  
   // 关闭线程池  
   pool.shutdown();  
   // 获取所有并发任务的运行结果  
   for (Future f : list) {  
    // 从Future对象上获取任务的返回值,并输出到控制台  
    System.out.println(">>>" + f.get().toString());  
   }  
   Date date2 = new Date();  
   System.out.println("----程序结束运行----,程序运行时间【"  
     + (date2.getTime() - date1.getTime()) + "毫秒】");  
}  
}  
class MyCallable implements Callable<Object> {  
private String taskNum;   
MyCallable(String taskNum) {  
   this.taskNum = taskNum;  
}  
public Object call() throws Exception {  
   System.out.println(">>>" + taskNum + "任务启动");  
   Date dateTmp1 = new Date();  
   Thread.sleep(1000);  
   Date dateTmp2 = new Date();  
   long time = dateTmp2.getTime() - dateTmp1.getTime();  
   System.out.println(">>>" + taskNum + "任务终止");  
   return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";  
}  
}

代码说明:
上述代码中Executors类,提供了一系列工厂方法用于创建线程池,返回的线程池都实现了ExecutorService接口。
public static ExecutorService newFixedThreadPool(int nThreads)
创建固定数目线程的线程池。
public static ExecutorService newCachedThreadPool()
创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
public static ExecutorService newSingleThreadExecutor()
创建一个单线程化的Executor。
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)
创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。

ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,返回Future。如果Executor后台线程池还没有完成Callable的计算,这调用返回Future对象的get()方法,会阻塞直到计算完成。
--------------------------------------前三种创建线程方法对比--------------------------------------
实现Runnable和实现Callable接口的方式基本相同,不过是后者执行call()方法有返回值,后者线程执行体run()方法无返回值,因此可以把这两种方式归为一种这种方式与继承Thread类的方法之间的差别如下:
1、线程只是实现Runnable或实现Callable接口,还可以继承其他类;
2、这种方式下,多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形;
3、但是编程稍微复杂,如果需要访问当前线程,必须调用Thread.currentThread()方法;
4、继承Thread类的线程类不能再继承其他父类(Java单继承决定)。

注:一般推荐采用实现接口的方式来创建多线程

生命周期
Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:

1、新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
2、就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件
3、运行:当就绪的线程被调度并获得处理器资源时,便进入运行状态, run()方法定义了线程的操作和功能
4、阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
5、死亡:线程完成了它的全部工作或线程被提前强制性地中止

1人推荐
随时随地看视频
慕课网APP