tip: 作为程序员一定学习编程之道,一定要对代码的编写有追求,不能实现就完事了。我们应该让自己写的代码更加优雅,即使这会费时费力。
推荐:体系化学习Java(Java面试专题)
文章目录
- 创建方式
- 1、继承Thread类并重写run()方法
- 2、 实现Runnable接口并重写run()方法
- 3、实现Callable接口并重写call()方法
- 4、使用线程池创建线程
创建方式
在Java中,线程可以通过以下4种方式创建,这4种方式可以让我们的程序异步执行:
- 继承Thread类并重写run()方法
- 实现Runnable接口并重写run()方法
- 实现Callable接口并重写call()方法
- 使用线程池创建线程
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 去使用。