天天看点

Java中有哪几种方式来创建线程来执行任务

作者:架构小哥

前言

在java中一个线程是使用Thread的实例来表示,所有线程对象都必须是Thread的子类实例或是Thread的实例,它位于java.lang包中,是Java中非常常用也是非常重要的一个基础类。Thread类实现了Runnable接口,它有不少非常重要的方法和属性:

Java中有哪几种方式来创建线程来执行任务
Java中有哪几种方式来创建线程来执行任务

那么创建Java的线程一般有哪几种方式呢?

一、继承Thread类创建线程类

1. 定义Thread类的子类ThreadDemo,并重写该Thread类的run()方法,在run()方法中执行该线程的主要逻辑。

2. 在主线程上创建Thread子类的实例,即创建了一个线程对象。

3. 调用线程对象的start()方法来启动该线程。

// 继承Thread类,创建一个新的线程类
public class ThreadDemo extends Thread{
    // 重写了Thread类的run()方法,将需要并发执行的用户业务代码编写在继承的run()方法中
    @Overrite
    public void run(){
        System.out.println(getName()+" 这里执行线程的业务逻辑");
    }
}           
public class AppDemo {
    public static void main(String[] args) {
        ThreadDemo threadDemo = new ThreadDemo();
        // 启动线程
        threadDemo.start();
        System.out.println("线程名:"+Thread.currentThread().getName()+" 运行结束");
    }
}           

执行结果:

线程名:main 运行结束

Thread-0 这里执行线程的业务逻辑

二、实现Runnable接口,创建线程目标类

有了Thread类了,为什么还需要多一个Runnable的接口方式来创建一个线程呢?那是因为Java继承的一个特征,extend只能一个父类,当一个子类已经继承了一个父类时,此时你希望这个类成为一个线程类,是不行的,但它能实现Runnable接口。

1.定义Runnable接口的实现类,并实现run()方法,在run()方法中执行该线程的主要逻辑。

⒉.创建Runnable实现类的实例,并将其作为Thread的target来创建Thread对象,Thread对象为线程对象。

3.调用线程对象的start()方法来启动该线程。

public class RunnableThreadDemo implements Runnable {

    // 重写了Runnable类的run()方法,将需要并发执行的用户业务代码编写在继承的run()方法中
    @0verrite
    public void run( ){
        System.out.println(Thread.currentThread().getName()+" 这里执行线程的业务逻辑" )
    }
}           
public class AppDemo {
    public static void main(String[] args) {
        RunnableThreadDemo threadDemo = new RunnableThreadDemo();
        // 启动线程
        threadDemo.start();
        System.out.println("线程名:"+Thread.currentThread().getName()+" 运行结束");
    }
}           

执行结果:

线程名:main 运行结束

Thread-0 这里执行线程的业务逻辑

三、使用匿名内部类,实现创建Thread子类

跟方式一类似,只是不需要创建一个单独的类文件来继承Thread,而是使用了匿名类来完成,代码如下:

public class AppDemo {
    public static void main(String[] args) {
        Thread thread = new Thread() {
            @Override
            public void run() {
                System.out.println(getName()+" 这里执行线程的业务逻辑");
            }
        };
        thread.start();
        System.out.println("线程名:"+Thread.currentThread().getName()+" 运行结束");
    }
}           

执行结果:

线程名:main 运行结束

Thread-0 这里执行线程的业务逻辑

四、使用匿名内部类,实现Runnable接口

跟方式二类似,区别在于使用匿名类来完成,代码如下:

public class AppDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable(){
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName()+" 这里执行线程的业务逻辑" );
            }
        });
        thread.start();
        System.out.println("线程名:"+Thread.currentThread().getName()+" 运行结束");
    }
}           

执行结果:

Thread-0 这里执行线程的业务逻辑

线程名:main 运行结束

什么?这跟方式二执行的结果不一样?这里是没问题的,这里是两个线程在跑着,哪个线程先跑是跟cpu的调度有关,跟代码的先后无关。这点请紧记。

五、使用lambda表达式来完成

使用lambda表达式使得代码更简洁,实现其实跟方式三一样,只是使用lambda来代替匿名类。

public class AppDemo {
    public static void main(String[] args) {
        Thread thread = new Thread(()->{
            System.out.println(Thread.currentThread().getName()+" 这里执行线程的业务逻辑");
        });
        thread.start();
        System.out.println("线程名:"+Thread.currentThread().getName()+" 运行结束");
    }
}           

执行结果:

线程名:main 运行结束

Thread-0 这里执行线程的业务逻辑

六、实现Callable接口

继承Thread类或者实现Runnable接口这两种方式来创建线程类有一个缺陷,就是不能获取异步执行的结果,即在主线程启动子线程后,能run起来就分道扬镳了,但在很多场景上需要获取到异步执行的结果,这个时候就需要实现Callable接口了。这是在1.5之后提供的一种创建多线程的方法,通过Callable接口和FutureTask类互相作用来完成线程执行以及在主线程中获取执行的结果。

Callable接口位于java.util.concurrent包中:

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}           

创建步骤如下:

  1. 首先定义一个 Callable 的实现类,并实现call方法。
  2. call方法是带返回值的。然后通过FutureTask的构造方法,把这个Callable 实现类传进去。
  3. 把 FutureTask作为 Thread 类的 target ,创建Thread线程对象。
  4. 通过FutureTask 的get方法获取线程的执行结果。
public class CallableTaskDemo implements Callable {
    // 编写好异步执行的具体逻辑,可以有返回值。
    // (Runnable接口中的run()方法是没有返回值得,Callable接口的call()方法有返回值)
    @Override
    public Long call() throws Exception {
        Long startTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+" 线程开始运行");
        Thread.sleep(1000);

        for(int i=0;i<100000000;i++){
            int j = i*10000;
        }

        Long endTime = System.currentTimeMillis();
        Long used = endTime-startTime;
        System.out.println(Thread.currentThread().getName()+" 线程结束运行");
        return used;
    }
}           
public class AppDemo {
    public static void main(String[] args) throws InterruptedException {
        CallableTaskDemo callableTaskDemo = new CallableTaskDemo();
        FutureTask<Long> futureTask = new FutureTask<Long>(callableTaskDemo);
        Thread thread = new Thread(futureTask,"callableTaskThread");
        thread.start();

        Thread.sleep(500);

        System.out.println("main线程执行一会");
        for(int i=0;i<100000000/2;i++){
            int j = i*10000;
        }
        // 获取并发任务的执行结果
        try {
            System.out.println(thread.getName()+" 线程占用时间:"+futureTask.get());
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}           

执行结果:

callableTaskThread 线程开始运行

main线程执行一会

callableTaskThread 线程结束运行

callableTaskThread 线程占用时间:1008

七、使用自带线程池

使用线程池的一个很重要的原因是线程的复用性。我们知道线程资源在服务器上来看,是一种非常昂贵的资源,如果使用不当,服务器的负载是非常高,并且可能会出现超负荷的情况,往往会导致服务器死机等问题,所以线程的合理分配就变得是一个非常有意思的话题。线程池往往就能很好地解决这个问题,设定一个线程池,往线程池中注入固定的线程数,超过的线程的任务数,要不等待,要不抛弃。大大地复用了线程,减少线程频繁创建与销毁,提高了服务器的可用率。

Java在线程池上也有多种类型的支持,在不同的场景下可选择不同类型的线程池。

  • FixThreadPool(int n);--固定大小的线程池

    使用于负载比较重的服务器,满足资源管理需求而对资源有强制限制的需求

  • SingleThreadPoolExecutor --单线程池

    需要保证顺序执行各个任务的场景,任务按先进先出的形式来执行

  • CashedThreadPool() -- 缓存线程池

    适用于提交短期的异步小程序以及负载较轻的服务器。当提交任务速度高于线程池中任务处理速度时,缓存线程池会不断地创建线程。调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。

  • ScheduledExecutor --定时及周期性的任务执行的线程池

    适用于延迟或调度类的工作,多数情况可替代Timer类

一般的使用方式如下:

  1. 首先,定义一个 Runnable 的实现类,重写run方法
  2. 然后创建一个拥有固定线程数的线程池
  3. 最后通过ExecutorService对象的execute 方法传入线程对象
public class RunnableTaskDemo implements Runnable {
    @Override
    public void run() {
        for(int i=0;i<3;i++){
            System.out.println(Thread.currentThread().getName()+" 轮次:"+i);

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }           
public class CallableTaskDemo implements Callable {
    @Override
    public Long call() throws Exception {
        Long startTime = System.currentTimeMillis();
        System.out.println(Thread.currentThread().getName()+" 线程开始运行");
        for(int i=0;i<3;i++){
            System.out.println(Thread.currentThread().getName()+" 轮次:"+i);
            Thread.sleep(1000);
        }
        Long used = System.currentTimeMillis()-startTime;
        System.out.println(Thread.currentThread().getName()+" 线程结束运行");
        return used;
    }
}           
public class AppDemo {
    private static ExecutorService threadpool = Executors.newFixedThreadPool(3);

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 1、执行Runnbale类型的target目标实例,无返回
        threadpool.execute(new RunnableTaskDemo());

        //2、 执行Runnbale类型的target目标实例,无返回,内部类形式
        threadpool.execute(new Runnable() {
            @Override
            public void run() {
                for(int i=0;i<3;i++){
                    System.out.println(Thread.currentThread().getName()+" 轮次:"+i);

                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }       
        });

        // 3、提交Callable执行目标实例,有返回
        Future future = threadpool.submit(new CallableTaskDemo());
        System.out.println("异步执行的结果为:" + future.get());
    }
}           

执行结果:

pool-1-thread-1 轮次:0

pool-1-thread-2 轮次:0

pool-1-thread-3 线程开始运行

pool-1-thread-3 轮次:0

pool-1-thread-2 轮次:1

pool-1-thread-1 轮次:1

pool-1-thread-3 轮次:1

pool-1-thread-2 轮次:2

pool-1-thread-1 轮次:2

pool-1-thread-3 轮次:2

pool-1-thread-3 线程结束运行

异步执行的结果为:3002

继续阅读