Java创建线程的四种方式及其比较
Java线程常用方法详解Java的线程状态及其相互转换

一、继承Thread类

1、介绍

其实Thread类本身也实现了Runnable接口,代表一个线程的实例。启动线程的唯一方法就是通过 Thread 类的 start()实例方法。start()方法是一个 native 方法,它将启动一个新线 程,并执行 run()方法。

2、使用

public class MyThread extends Thread { 
  public void run() { 
   System.out.println("MyThread.run()");
  }
}
MyThread myThread1 = new MyThread(); 
myThread1.start();

再如

// 创建线程对象
Thread t = new Thread() { 
    public void run() { 
        // 要执行的任务
    }
};
// 启动线程 t.start();

二 、实现Runnable类

1、介绍

使用Runnable配合Thread

把【线程】和【任务】(要执行的代码)分开 Thread 代表线程
Runnable 可运行的任务(线程要执行的代码)

2、使用

步骤:

  • 1、定义一个类实现Runnable接口;
  • 2、创建该类的实例对象obj;
  • 3、将obj作为构造器参数传入Thread类实例对象,这个对象才是真正的线程对象;
  • 4、调用线程对象的start()方法启动该线程;
Runnable runnable = new Runnable() { 
    public void run(){
        // 要执行的任务
    }
};
// 创建线程对象
Thread t = new Thread( runnable ); 
// 启动线程
t.start();

用 Runnable 更容易与线程池等高级 API 配合
用 Runnable 让任务类脱离了 Thread 继承体系,更灵活

再如:

public class MyThread {

    public static void main(String ards[]){
        Runnable implRunnable = new ImplRunnable();
        for(int i=0;i<10;i++){
            new Thread(implRunnable).start();
        }
        System.out.println(Thread.currentThread().getName());
    }
    
}
//实现Runnable类,并重写run方法
class ImplRunnable implements Runnable{
    private volatile  int i = 0;
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"--"+ i++);
        
    }
}

不论创建多少个线程,只需要创建一个Runnable接口实现类的对象

启动线程,new Thread(Runnable接口实现类的对象).start()

创建线程调用的是Thread类Runable类型参数的构造器

三、使用Callable和Future接口

重写call方法(有返回值

call方法比run方法更强大:有返回值,且可以声明抛出异常

1、介绍

有返回值的任务必须实现 Callable 接口,类似的,无返回值的任务必须 Runnable 接口。执行 Callable 任务后,可以获取一个 Future 的对象,在该对象上调用 get 就可以获取到 Callable 任务 返回的 Object 了,再结合线程池接口 ExecutorService 就可以实现传说中有返回结果的多线程 了。

2、使用

步骤:

  1. 创建一个Callable接口实现的对象,并重写call方法
  2. 创建一个FutureTask对象,传入Callable类型的参数,并接受返回值
  3. 使用FutureTask对象作为Thread对象的target创建并启动线程(因为FutureTask实现了Runnable接口)
  4. 调用FutureTask对象的get方法来获得子线程执行结束后的返回值
public class MyThread {

    public static void main(String ards[]) throws InterruptedException, ExecutionException{

        for(int i=0;i<10;i++){
            Callable<Integer> implCallable = new ImplCallable();
            FutureTask<Integer> futureTask = new FutureTask<Integer>(implCallable);
            new Thread(futureTask).start();
            System.out.println(Thread.currentThread().getName()+"----"+futureTask.get());
        }

        System.out.println(Thread.currentThread().getName());
    }
    
}

class ImplCallable implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        int result = 0;
        for(int i=0;i<10;i++){
            result += i;
        }
        System.out.println(Thread.currentThread().getName());
        return result;
    }

}

3、Future接口控制Callable的方法

>boolean cancel(boolean mayInterruptIfRunning):视图取消该Future里面关联的Callable任务

>V get():返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值

>V get(long timeout,TimeUnit unit):返回Callable里call()方法的返回值,最多阻塞timeout时间,经过指定时间没有返回抛出TimeoutException

>boolean isDone():若Callable任务完成,返回True

>boolean isCancelled():如果在Callable任务正常完成前被取消,返回True

四、基于线程池

1、介绍

线程和数据库连接这些资源都是非常宝贵的资源。那么每次需要的时候创建,不需要的时候销 毁,是非常浪费资源的。那么我们就可以使用缓存的策略,也就是使用线程池。

关于线程池的接口和实现类(注意他们的实现阶层实现关系)

Executor 负责现成的使用和调度的根接口

|–ExecutorService 线程池的主要接口

|–ThreadPoolExecutor 线程池的实现类

|–ScheduledExecutorService 接口,负责线程的调度

|–ScheduledThreadPoolExecutor (extends ThreadPoolExecutor implements ScheduledExecutorService)

为Executors工具类提供了创建线程池的方法

2、使用

例1、线程池和Runnable接口

public class ThreadPool {
    public static void main(String[] args){

        //使用Executors工具类中的方法创建线程池
        ExecutorService pool = Executors.newFixedThreadPool(5);

        ThreadPoolDemo demo = new ThreadPoolDemo();

        //为线程池中的线程分配任务,使用submit方法,传入的参数可以是Runnable的实现类,也可以是Callable的实现类
        for(int i=1;i<=5;i++){
            pool.submit(demo);
        }

        //关闭线程池
        //shutdown : 以一种平和的方式关闭线程池,在关闭线程池之前,会等待线程池中的所有的任务都结束,不在接受新任务
        //shutdownNow : 立即关闭线程池
        pool.shutdown();


    }
}
class ThreadPoolDemo implements Runnable{

    /**多线程的共享数据*/
    private int i = 0;

    @Override
    public void run() {
        while(i<=50){
            System.out.println(Thread.currentThread().getName()+"---"+ i++);
        }
    }
}

例2、线程池和Callable接口

public class ThreadPool2 {
    
    public static void main(String args[]){
        ExecutorService executorService = Executors.newFixedThreadPool(5);
        
        for(int i=0;i<5;i++){
            Future<Integer> future = executorService.submit(new Callable<Integer>() {

                @Override
                public Integer call() throws Exception {
                    int result = 0;
                    for(int i=0;i<=10;i++){
                        result += i;
                    }
                    return result;
                }
            });
            
            try {
                System.out.println(Thread.currentThread().getName()+"--"+future.get());
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }
        
        executorService.shutdown();

    }

}

五、创建方法的比较

基于线程池的创建方法只是在前几种创建方式的基础上增加了线程池,而实现Runnable和实现Callable接口的方式基本相同,不过是后者执行call()方法有返回值,因此,我们可以把这两种方法归为一种方式。因此,我们只要比较实现接口与继承Thread类:

1、继承Thread的优缺点

优点:

实现起来简单,而且要获取当前线程,无需调用Thread.currentThread()方法,直接使用this即可获取当前线程;

缺点:

  • 线程类已经继承Thread类了,就不能再继承其他类;
  • 多个线程不能共享同一份资源(如前面分析的成员变量 i );

2、实现接口的优缺点

优点:

  • 线程类只是实现了接口,还可以继承其他类;
  • 多个线程可以使用同一个target对象,适合多个线程处理同一份资源的情况。

缺点:

  • 通过这种方式实现多线程,相较于第一类方式,编程较复杂;
  • 要访问当前线程,必须调用Thread.currentThread()方法。

注:一般采用实现接口的方法实现多线程