tip: 作为程序员一定学习编程之道,一定要对代码的编写有追求,不能实现就完事了。我们应该让自己写的代码更加优雅,即使这会费时费力。

推荐:体系化学习Java(Java面试专题)


文章目录

  • 创建方式
  • 1、继承Thread类并重写run()方法
  • 2、 实现Runnable接口并重写run()方法
  • 3、实现Callable接口并重写call()方法
  • 4、使用线程池创建线程


创建方式

在Java中,线程可以通过以下4种方式创建,这4种方式可以让我们的程序异步执行:

  1. 继承Thread类并重写run()方法
  2. 实现Runnable接口并重写run()方法
  3. 实现Callable接口并重写call()方法
  4. 使用线程池创建线程

1、继承Thread类并重写run()方法

这种方式是最常见的创建线程的方式,需要创建一个继承Thread类的子类,并重写run()方法,在run()方法中定义线程要执行的任务。然后,创建该子类的对象并调用start()方法启动线程。

注意启动是调用 start 方法,而不是 run 方法,启动后线程就处于 new (新建)的状态,至于什么时候运行,那是不可控的,交给 CPU,等到分配到可运行的时间片的时候,就可以运行。

package com.pany.camp.thread;

public class MyThread extends Thread {

    @Override
    public void run() {
        System.out.println("my thread");
    }

    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
    }
}

2、 实现Runnable接口并重写run()方法

这种方式是另一种创建线程的常用方式。需要创建一个实现Runnable接口的类,并重写run()方法,在run()方法中定义线程要执行的任务。然后,创建该类的对象并将其作为参数传递给Thread类的构造函数,最后调用start()方法启动线程。

package com.pany.camp.thread;

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("my runnable");
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();

        // 也支持 lambda 表达式
        Thread thread1 = new Thread(() -> {
            System.out.println("my runnable");
        });
        thread1.start();
    }
}

3、实现Callable接口并重写call()方法

这种方式与实现Runnable接口的方式类似,不同之处在于,Callable接口的call()方法可以返回一个结果,并且可以抛出异常。需要创建一个实现Callable接口的类,并重写call()方法,在call()方法中定义线程要执行的任务。然后,创建该类的对象并将其作为参数传递给ExecutorService类的submit()方法,最后调用Future类的get()方法获取call()方法的返回结果。

package com.pany.camp.thread;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class MyCallable implements Callable<Integer> {
    private int num;

    public MyCallable(int num) {
        this.num = num;
    }

    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= num; i++) {
            sum += i;
        }
        return sum;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable myCallable = new MyCallable(100);
        FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
        Thread thread = new Thread(futureTask);
        thread.start();
        int result = futureTask.get();
        System.out.println("1到100的和为:" + result);
    }
}

在这个例子中,我们定义了一个名为MyCallable的类,实现了Callable接口,并重写了call()方法。在call()方法中,我们计算了从1到num的所有整数的和,并返回了这个结果。注意,我们在Callable接口中指定了返回值的类型为Integer。这意味着我们的call()方法必须返回一个Integer对象。

4、使用线程池创建线程

线程池是一种管理线程的机制,可以重复利用已经创建的线程,避免频繁地创建和销毁线程的开销。可以使用Executors类的静态方法创建不同类型的线程池,然后将Runnable或Callable对象提交给线程池执行。

package com.pany.camp.thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolExample {

    public static void main(String[] args) {
        // 创建一个线程池,其中包含2个线程
        ExecutorService executor = Executors.newFixedThreadPool(2);

        // 创建10个任务并提交给线程池
        for (int i = 1; i <= 10; i++) {
            Runnable worker = new WorkerThread("任务 " + i);
            executor.execute(worker);
        }

        // 关闭线程池
        executor.shutdown();
        while (!executor.isTerminated()) {
        }

        System.out.println("所有任务执行完成");
    }
}
package com.pany.camp.thread;

public class WorkerThread implements Runnable {

    private String taskName;

    public WorkerThread(String taskName) {
        this.taskName = taskName;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 开始执行 " + taskName);
        try {
            // 模拟任务执行时间
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + " 完成执行 " + taskName);
    }
}

在这个例子中,我们使用 ExecutorService 和 Executors 类创建了一个线程池。 newFixedThreadPool(2) 方法创建了一个包含2个线程的线程池。然后,我们创建了10个任务,并将它们提交给线程池。每个任务都是一个 WorkerThread 对象,实现了 Runnable 接口,重写了 run() 方法。在 run() 方法中,我们打印了任务名称,并模拟了任务执行时间。最后,我们调用 executor.shutdown() 方法关闭线程池,并等待所有任务执行完成。在任务执行完成后,我们打印了一条消息。

但是注意的是,上面这种 ExecutorService 去创建线程池的方式是不推荐的,因为线程池里面使用无界队列可能引起内存溢出,所以建议自己封装 ThreadPoolExecutor 去使用。