Java实现多线程的4种方式

巧了我就是萌 提交于 2020-02-09 18:21:02

 

Java多线程实现的方式有4种:

1. 继承Thread类,重写run方法。

2. 实现Runnable接口,实现run方法,实现Runnable接口的实现类的实例对象作为Thread构造函数的target。

3. 通过线程池创建线程,即Executor。

4. 实现Callable线程接口(有返回值)。

 

- 前面3种可以归结为一类:无返回值,原因很简单,通过重写run方法,run方式的返回值是void,所以没有办法返回结果。

- ”实现Callable线程接口“这个方式可以归结成一类:有返回值,实现Callable接口,就要实现call方法,这个方法的返回值可由泛型指定。

1 继承Thread类,重写run方法

1、Thread类位于java.lang包中,Thread的每个实例对象就是一个线程,它的子类的实例也是一个线程。

2、我们通过Thread类或它的派生类才能创建线程的实例并启动一个新的线程。

class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println("子线程run()方法执行----");
    }
}

2 实现Runnable接口,实现run方法

1、通过继承Thread类来创建线程,有一个缺点,如果我们的类已经从一个类继承,则无法再继承Thread类。

2、我们可以通过声明自己的类实现Runnable接口来创建线程。Runnable接口只有一个方法run(),我们的类需要实现这个方法。

3、实现Runnable接口来创建线程的步骤如下:

(1)定义Runnable接口的实现类,并实现该接口的run()方法。

(2)创建Runnable实现类的实例,并以此实例作为Thread类的target参数来创建Thread线程对象,该Thread对象才是真正的线程对象。

 

3 使用线程池的Executor(线程执行器)

1、java SE5的java.util.concurrent包中的Executor(执行器)将为你管理Thread对象,从而简化多线程编程。

2、Executor在客户端和任务之间提供了一个中间层,与客户端直接执行任务不同,这个中介对象将执行任务。

3、Executor允许你管理异步任务的执行,无须显式地管理线程的生命周期。Executor在Java SE5/6是启动线程的优选方法。

4、java里面的线程池的顶级接口是Executor,Executor并不是一个线程池,而只是一个执行线程的工具,而真正的线程池是ExecutorService。

使用demo

创建一个LiftOff类:

public class LiftOff extends Thread {

    private String name;

    public LiftOff(String name) {
        this.name = name;
    }

    @Override
    public void run() {
        super.run();
        System.out.println("LiftOff.run(): the name is " + name);
    }
}

测试代码:

/**
 * @author chenlw
 * @date 2020/02/07
 */
public class ExecutorTester {

    public static void main(String[] args) {
        testCachedThreadPool();
    }

    public static void testCachedThreadPool() {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            executorService.execute(new LiftOff("LiftOff" + i));
        }
        executorService.shutdown();
    }

}

通常,单个的Executor被用来创建和管理系统中的任务。

对shutdown()方法的调用可以防止新任务被提交到这个Executor,当前线程(在本例当中指main()主线程)将继续运行在shutdown()被调用前提交的所有任务。这个程序将在Executor中的所有任务完成后尽快推出。

 

Executor常用的类型有以下几种。

CachedThreadPool、FixedThreadPool、SingleThreadExecutor。

3.1 CachedThreadPool(可缓存线程池程)

CachedThreadPool在程序运行当中通常会创建与任务数量相同的线程,然后在它回收线程的时候停止创建新线程,因此它是合理的Executor的首选。

 

3.2 FixedThreadPool(定长线程池)

1、一次性预先执行代价高昂的线程分配,因而也就可以限制线程的数量了。这可以节省时间。

2、FixedThreadPool会创建有数量限制的线程池,这样可以控制资源的开销。

3、使用场景:如果希望创建特定数量的线程池,可以使用FixedThreadPool,初始化时设定线程池的数量。

 

3.3 SingleThreadExecutor(单线程化的线程池)

1、它就像线程数量为1的FixedThreadPool。这对于希望在另一个线程中连续运行的事物(长期存活的任务)来说是很有用的,例如监听进入套接字连接的任务。

2、如果向SingleThreadExecutor提交了多个任务,那么这些任务将排队,每个任务都会在下一个任务开始之前运行结束,所有的任务都将使用相同的线程。

3、SingleThreadExecutor会序列化所有提交给它的任务,并且会维护它自己内部的悬挂任务队列。

 

使用线程池的优点

1.重用线程池的线程,避免因为线程的创建和销毁锁带来的性能开销。

2.有效控制线程池的最大并发数,避免大量的线程之间因抢占系统资源而阻塞。

3.能够对线程进行简单的管理,并提供一下特定的操作如:可以提供定时、定期、单线程、并发数控制等功能。

 

4 实现Callable接口(线程方法有返回值)

1、如果希望线程方法处理完任务返回一些数据,那么可以使用Callable接口。

2、在Java SE5引入的Callable接口是一种具有类型参数的泛型,它的类型参数表示的是方法call()返回的类型。

3、下面是一个简单示例:

class TaskWithResult implements Callable<String> {

    private int id;

    public TaskWithResult(int id) {
        this.id = id;
    }

    @Override
    public String call() throws Exception {
        return "result of TaskWithResult is " + id;
    }
}

/**
 * @author chenlw
 * @date 2020/02/07
 */
public class CallableTester {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        List<Future<String>> results = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            // submit()方法会返回Future对象,它用Callable返回结果的特定类型进行了参数化。
            results.add(executorService.submit(new TaskWithResult(i)));
        }
        for (Future<String> future : results) {
            try {
                System.out.println("isDone(): "+future.isDone()+", "+" get():"+future.get());
            } catch (InterruptedException e1) {
                System.out.println(e1.getMessage());
            } catch (Exception e2) {
                System.out.println(e2);
            } finally {
                executorService.shutdown();
            }
        }
    }
}

运行结果:

isDone(): true,  get():result of TaskWithResult is 0
isDone(): true,  get():result of TaskWithResult is 1
isDone(): true,  get():result of TaskWithResult is 2
isDone(): true,  get():result of TaskWithResult is 3
isDone(): true,  get():result of TaskWithResult is 4
isDone(): true,  get():result of TaskWithResult is 5
isDone(): true,  get():result of TaskWithResult is 6
isDone(): true,  get():result of TaskWithResult is 7
isDone(): true,  get():result of TaskWithResult is 8
isDone(): true,  get():result of TaskWithResult is 9

- submit()方法会返回Future对象,它用Callable返回结果的特定类型进行了参数化。

- 我们可以用isDone()方法来查询Future是否已经完成。当任务完成时,它会有一个结果。

- 另外,我们也可以直接调用get()方法来获取结果,在这种情况下,get()方法可能会被阻塞,直到线程返回结果数据。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!