Java中创建线程的几种方式
# Java中创建线程的几种方式
# 继承Thread类
public class Demo1 {
public static void main(String[] args) {
System.out.println("hello world");
ATest aTest = new ATest();
aTest.start();
}
}
class ATest extends Thread{
@Override
public void run() {
System.out.println("this is a new Thread");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
继承Thread类,并重写run()
方法(run方法就是该线程要做的事情)。
最后使用该类调用start()
即可开启一个线程。
Java中的类是单继承,但是类可以实现多个接口。
该种方法的弊端很明显,因为类是单继承,如果要想继承一些其他类就无法实现了。因此该种开启线程的方法很少用。
# 实现Runnable接口
public class Demo2 implements Runnable{
@Override
public void run() {
System.out.println(1024);
}
public static void main(String[] args) {
Thread thread = new Thread(new Demo2());
thread.start();
}
}
2
3
4
5
6
7
8
9
10
11
12
定义一个类,继承Runnable
接口,并重写run()
方法。
然后创建一个Thread对象(A),并使用实现Runnable接口
的类创建对象,并将其作为参数传给Thread的构造方法。
最后 A 调用start()
方法即可开启一个新的线程。
Thread th2 = new Thread(()-> System.out.println("this is lambda express"));
th2.start();
2
当然在创建的时候,你还可以使用匿名内部类的方式进行创建,如果要执行的内容很少,还可以用lambda表达式进行简写。
# Runnable比Thread好的地方
下面我们来对刚才说的两种实现线程内容的方式进行对比,也就是为什么说实现 Runnable 接口比继承 Thread 类实现线程要好?好在哪里呢?
首先,我们从代码的架构考虑,实际上,Runnable 里只有一个 run() 方法,它定义了需要执行的内容,在这种情况下,实现了 Runnable 与 Thread 类的解耦,Thread 类负责线程启动和属性设置等内容,权责分明。
第二点就是在某些情况下可以提高性能。
因为,使用继承 Thread 类方式,每次执行一次任务,都需要新建一个独立的线程
,执行完任务后线程走到生命周期的尽头被销毁,如果还想执行这个任务,就必须再新建一个继承了 Thread 类的类,如果此时执行的内容比较少,比如只是在 run() 方法里简单打印一行文字,那么它所带来的开销并不大,相比于整个线程从开始创建到执行完毕被销毁,这一系列的操作比 run() 方法打印文字本身带来的开销要大得多,相当于捡了芝麻丢了西瓜,得不偿失。
如果我们使用实现 Runnable 接口
的方式,就可以把任务直接传入线程池
,使用一些固定的线程来完成任务,不需要每次新建销毁线程,大大降低了性能开销。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Executing task in " + Thread.currentThread().getName());
}
}
public class Main {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(10); // 创建一个固定大小的线程池
for (int i = 0; i < 100; i++) {
executor.submit(new MyRunnable());
}
executor.shutdown();
while (!executor.isTerminated()) {
// 等待所有任务完成
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
第三点好处在于 Java 语言不支持双继承,如果我们的类一旦继承了 Thread 类,那么它后续就没有办法再继承其他的类,这样一来,如果未来这个类需要继承其他类实现一些功能上的拓展,它就没有办法做到了,相当于限制了代码未来的可拓展性。
- 总结:解耦、更灵活、某些情况可能提高性能
# 实现Callable接口
它跟实现runnable接口类似,但有一个很大的不同点是:它可以获取线程执行完后的结果。
public class Demo3 implements Callable<String> {
@Override
public String call() {
System.out.println("this is a new Thread");
return "create";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask<String> futureTask = new FutureTask<>(new Demo3());
Thread th = new Thread(futureTask);
th.start();
String result = futureTask.get();
System.out.println(result);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
使用实现callable<v>
接口的方式来创建线程,需要配合FutureTask<v>
来实现
- 定义类,实现callable接口,并重写
call()
方法 - 创建
FutureTask
的对象,并将目标类的对象作为参数传入构造方法 - 然后创建
Thread
的对象,并用刚刚创建的FutureTask
的对象作为参数传入构造方法 - 最后调用
start()
方法,即可开启新的线程 - 而返回值的获取:
FutureTask
的对象调用get()
方法
# 通过线程池创建线程
public class Demo2 implements Runnable{
@Override
public void run() {
System.out.println("this is a new Thread");
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
executorService.execute(new Demo2());
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
首先定义一个线程池,我们这里定义数量为10,
接着我们将实现的Runnable 接口或者继承 Callable 接口
传入,让线程池进行调用,从而实现新线程的开启
在上述例子中,是
execute()
方法帮忙开启了线程关于通过线程池开启线程,实际上是不建议通过Executors来创建线程池,而是通过ThreadPoolExecutor来创建
因为,
Executors
可能会导致oom,此外还不能自定义线程的名称,不利于排查问题。
通过ThreadPoolExecutor
创建线程池
- 指定核心线程数、最大线程数、存活时间和工作队列
int corePoolSize = 2;
int maximumPoolSize = 4;
long keepAliveTime = 1000L;
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(10);
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, TimeUnit.MILLISECONDS, workQueue);
2
3
4
5
- 将任务提交给线程池执行,使用execute()方法或submit()方法
threadPoolExecutor.execute(task); // 立即返回,不等待任务执行结果
- 提交任务并返回一个Future对象,可以等待任务执行结果
Future<Result> future = threadPoolExecutor.submit(task);
- 关闭线程池,使用shutdown()方法或shutdownNow()方法
// 不再接受新任务,等待所有任务执行完毕后关闭线程池
threadPoolExecutor.shutdown();
// 尝试停止所有正在执行的任务,并返回等待执行的任务列表
threadPoolExecutor.shutdownNow();
2
3
4