线程的创建方式主要有三种,其中后两种通过实现接口的方式又可以用匿名内部类或者lambda表达式简化代码。
1. 继承Thread类
Thread是Java中线程对应的类,我们需要自定义一个类继承此类并重写run()方法,后续就能通过创建这个自定义类调用start()方法开辟一个新的线程执行run方法中的逻辑。
方法名 | 功能 |
---|---|
void run() | 线程中的执行逻辑 |
void start() | 开辟一个新的线程并执行run方法中的逻辑 |
static void sleep(long millis) | 使当前线程阻塞等待millis毫秒 |
public class Test {
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start(); //开辟新的线程执行run中的逻辑
//循环打印
while (true){
System.out.println("这是main线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
class MyThread extends Thread{
@Override
public void run(){
//循环打印
while (true){
System.out.println("这是Thread线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
执行结果:
从执行结果可以看出主线程和新开的线程并发执行
2. 实现Runnable
通过第一种方法创建线程的话会使执行逻辑与线程本身耦合在一起,如果要修改代码逻辑就得修改线程类MyThread。于是我们有了这种方法,把代码执行逻辑作为参数传输到Thread类中创建线程。这种方法的步骤是:
- 实现Runnable接口重写run方法,run方法中的逻辑为线程执行的逻辑
- 把Runnable作为Thread构造器的参数创建Thread对象t
- 调用t.start()方法创建新的线程。
public class Demo6 {
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread t = new Thread(runnable);
t.start();
while (true){
System.out.println("这是main线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
class MyRunnable implements Runnable {
@Override
public void run(){
while (true){
System.out.println("这是Runnable线程");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
执行结果:
3. 实现Callable接口
使用前两种方式创建的线程都是没有返回值的,假如我们需要返回线程中的计算结果就需要现在的这种方式。这种方式与上面的相比比较麻烦。这种方法的步骤是
- 实现Callable接口并重写call方法,call方法中的逻辑就是线程中要执行的逻辑
- 创建Callable实现类的对象作为FutureTask构造器的参数创建FutureTask的对象task。
- 把FutureTask的对象作为Thread构造器的参数创建Thread的对象。
- 调用thread.start()方法创建新的线程执行call方法中逻辑,然后在调用task.get()方法可以使主线程阻塞等待,直到thread新开的线程执行完,然后task.get()方法可以获取线程的执行结果并返回。
- 注意:如果直接主线程调用task.get()方法但是并没有调用thread.start()的话,就会使主线程一直阻塞等待,因为get()方法一直获取不到线程执行结果,就会一直阻塞等待。
public class Demo6 {
public static void main(String[] args) {
MyCallable callable = new MyCallable();
FutureTask<Integer> task = new FutureTask<>(callable);
Thread thread = new Thread(task);
thread.start();
try {
//获取线程执行结果并打印
System.out.println(task.get()); //会使主线程阻塞等待thread线程执行完并获取其执行结果返回
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
System.out.println("主线程执行结束");
}
}
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
Thread.sleep(1000); //让线程阻塞等待1秒
System.out.println("这是Callable线程");
return 20;
}
}
执行结果:
由执行结果可以看到,即使thread线程中阻塞了一秒,仍然是先打印“这是Callable线程”后打印返回结果和"主线程执行结束"。这也证明了get方法会使主线程阻塞等待thread线程执行完并获取其返回结果。
拓展
到这里三种主要方式已经学完了,但是对于第二和第三种方式我们都可以使用lambda表达式和匿名内部类的方式简化代码,这里附上通过这两种方式简化后的代码
通过匿名内部类简化
//实现Runnable方式创建
Thread t1 = new Thread(new Runnable(){
@Override
public void run(){
System.out.println("这是t1线程");
}
});
//实现Callable方式创建
Thread t2 = new Thread(new FutureTask<Integer>(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("这是t2线程");
return 2;
}
}));
通过lambda表达式简化
//实现Runnable方式创建
Thread t3 = new Thread(() -> System.out.println("这是t3线程"));
//实现Callable方式创建
Thread t4 = new Thread(new FutureTask<Integer>(()->{
System.out.println("这是t4线程");
return 4;
}));