Java-并发-Java多线程的4种实现方式详解

  Java中多线程的实现方式很多,本文介绍四种方法,分别是继承Thread、实现Runnable、实现Callable、使用线程池。在最后章节给出了全部方式的演示.

1 继承Thread类

1.1 步骤

  1. 创建一个类,继承Thread类,该类具备多线程的特征。
  2. 重写Tread类的run方法,在run方法当中定义线程任务。Run()方法体又叫线程执行体。
  3. 创建Tread类的子类的实例,即创建了线程对象。通过调用该对象的start方法开启一条新线程,调用start方法之后java虚拟机会自动调用该线程的run方法。
class Thread1 extends Thread {
    @Override
    public void run() {
        //线程任务....
        System.out.println("继承thread");
    }
}
Thread1 thread1 = new Thread1();
thread1.start();

1.2 为什么要继承Thread类?为什么不直接创建Thread对象?

  new Thread类得到一个线程对象是没有任何问题的,调用start()开启了一个新的线程,此时JVM会自动去调用run方法,但是run方法当中没有任何的内容;
  所以需要定义一个类,继承Thread类,此时就可以重写run方法,把线程任务定义在run方法当中即可。

1.3 Thread相关方法

    //获得当前线程对象;
    //当一个类继承Tread类时,run方法中直接使用this关键字即可获取当前线程对象,而this关键字可以省略。
    static Thread currentThread()

    //获得线程的名称:run方法中直接getName();
    //线程的名称:默认情况是Thread-0开始,数字依次往后增加,当然可以自定义线程的名称;每个线程都有一个标识名,并且多个线程可以同名。
    //主线程默认名称: main   主线程的执行体:main方法
    String getName()

    //自定义线程名称,run方法中直接setName(String name);
    void setName(String name)

    //测试线程是否处于活动状态。就绪态和运行态就是活动状态.
    boolean isAlive()

1.4 名称为什么是:Thread-? 编号

  这个简单,我们进入Thread类看看源码就知道了.

public class Thread implements Runnable {
    //定义名字的变量
    private volatile String name;
    //定义线程计数器
    private static int threadInitNumber;

    private static synchronized int nextThreadNum() {
        //调用该方法,计数器自增1
        return threadInitNumber++;
    }

    //构造器    
    Thread(Runnable target, AccessControlContext acc) {
        //调用构造器,此处将名字设为"Thread-",调用计数器方法
        init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
    }

    //init
    private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
        //给名字初始化
        this.name = name;
        //.....省略了代码
    }
}

2 实现Runnable接口

2.1 步骤

  1. 创建一个类A:实现Runnable接口,并重写run方法,在run方法内定义线程任务。
  2. 创建Runnable接口实现类的实例并且作为一个target传递给Thread类的构造器创建一个Thread类对象,此时Runnable接口和Thread类就具有联系,而该Thread对象才是真正的线程对象。
  3. 使用Thread对象的start方法开启一个新的线程

  注意:当类实现Runnable接口时,只能用Thread.currentThread()方法获取当前线程对象。

2.2 Runnable接口

  Runnable为非Thread子类的类提供了一种激活方式。激活的意思是说某个线程已启动并且尚未停止。通过实例化某个 Thread 实例并将自身作为运行目标,就可以运行实现 Runnable 的类而无需创建 Thread 的子类。
  大多数情况下,如果只想重写run()方法,而不重写其他Thread 方法,那么应使用 Runnable 接口。这很重要,因为除非程序员打算修改或增强类的基本行为,否则不应为该类创建子类。Java应该慎用继承。
  唯一的方法: void run(); 要想实现该接口必须重写该方法!

3 实现Callable接口

3.1 概述

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

  1. call()方法可以有返回值
  2. call()方法可以声明抛出异常

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

3.2 相关方法

    //试图取消该Future里面关联的Callable任务
    boolean cancel(boolean mayInterruptIfRunning)

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

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

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

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

3.3 步骤

  1. 创建一个类A实现Callable接口,重写call方法定义线程任务。
  2. 新建FutureTask类,传入类A,该类可以获取返回值。
  3. 新建Thread类传入FutureTask对象,使用start方法开启线程。
  4. 使用FutureTask类的get方法获取返回值。

3.4 三种实现方式的比较

3.4.1 继承Thread类

  1. 实现简单,直接继承Thread即可。并且在run()方法内获取当前线程直接使用this
    就可以了,无须使用Thread.currentThread() 方法;
  2. java当中的继承只能是单继承,一个类只能继承一个直接父类。如果一个类继承了Thread类,就不能有其他的父类。
  3. 创建的对象,即封装了线程任务,又封装了线程对象的特点(线程对象),不同的东西封装到了一个对象之中,不符合java面向对象的特点。

3.4.2 实现Runnable接口

  1. run()方法内获取当前线程不能使用使用this,必须使用Thread.currentThread() 方法;
  2. 将线程任务单独的封装到一个接口实现类当中,将线程任务和线程对象进行了分离:Runnable接口实现类当中定义线程任务,Thread类封装了线程对象,将不同的功能封装到不同的对象之中,这种思想更加的符合面向对象的特点。
  3. 避免了单继承带来的局限性。
  4. 适合多条线程处理同一个资源的情况,很容易的实现资源共享。需要多个线程完成一个任务时,比如:多个线程共同卖100张票,他们需要共享100张票这个资源。

3.4.3 实现Callable接口

  具有实现Runnable接口的特点,同时还能获取返回值,并且可以抛出异常。

4 使用线程池工具

  线程池提供了一个线程队列,队列中保存着所有等待状态的线程,避免了创建与销毁额外开销,提高了响应的速度,建议大家使用这种方法创建、管理线程。
体系结构:
java.util.concurrent.Executor : 负责线程的使用与调度的根接口
  |–ExecutorService 子接口: 线程池的主要接口
    |–ThreadPoolExecutor 线程池的实现类
      |–ScheduledExecutorService 子接口:负责线程的调度
      |–ScheduledThreadPoolExecutor :继承 ThreadPoolExecutor,实现 ScheduledExecutorService

工具类 : Executors
  ExecutorService newFixedThreadPool() : 创建固定大小的线程池
  ExecutorService newCachedThreadPool() : 缓存线程池,线程池的数量不固定,可以根据需求自动的更改数量。
  ExecutorService newSingleThreadExecutor() : 创建单个线程池。线程池中只有一个线程
  ScheduledExecutorService newScheduledThreadPool() : 创建固定大小的线程,可以延迟或定时的执行任务。

5 多线程创建方式演示

public class ThreadCreate {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //main线程
        System.out.println("main: " + Thread.currentThread().getName());

        //方式一
        Thread1 thread1 = new Thread1();
        thread1.start();
        //方式二
        Thread thread = new Thread(new Thread2());
        thread.start();

        //方式三
        FutureTask<String> stringFutureTask = new FutureTask<String>(new Thread3());
        Thread thread2 = new Thread(stringFutureTask);
        thread2.start();
        //获取返回值
        String s = stringFutureTask.get();
        System.out.println(s);

        //方法四:线程池
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        executorService.execute(() -> System.out.println("线程池方式: " + Thread.currentThread().getName()));
        executorService.shutdown();
    }


}

class Thread1 extends Thread {
    @Override
    public void run() {
        System.out.println("继承THread: " + this.getName());
    }
}

class Thread2 implements Runnable {
    @Override
    public void run() {
        System.out.println("实现Runnable: " + Thread.currentThread().getName());
    }
}

class Thread3 implements Callable {
    @Override
    public Object call() {
        System.out.print("实现Callable: ");
        return Thread.currentThread().getName();
    }
}
发布了29 篇原创文章 · 获赞 47 · 访问量 8203

猜你喜欢

转载自blog.csdn.net/weixin_43767015/article/details/104807736