最近面试经常被问到多线程的一些知识,最近可能会系统的学习一下多线程,然后也会写笔记进行总结。一起学的小伙伴可以可以交流一下
创建线程的几种方式
- 继承Thread类
- 实现Runable接口
- 实现 Callable接口
- 匿名内部类 使用
- 线程池
创建线程的本质
以上几种方式本质上还是就两种实现Runnable接口以及 继承Thread类。如果再细致一些就是实现了Runable接口我们可以看到Thread类内部就是实现Runable接口。
下面先看五种方式创建线程,最后看一下Thread线程的源码是如何实现的
-
继承Thread类
class tets3 extends Thread{ @Override public void run() { System.out.println("线程3 启动"); } } test3 thread=new test3(); thread.start(); -
实现Runnable接口
class test implements Runnable{ @Override public void run() { System.out.println("test implement Runnable"); } } test test=new test(); Thread thread=new Thread(test); thread.start();将test作为参数传入Thread类
-
实现Callable接口
class test2 implements Callable<String>{ @Override public String call() throws Exception { return null; } } FutureTask<String> mv=new FutureTask<String>(()->{ System.out.println(); return "a"; }); Thread thread1=new Thread(mv); thread1.start(); System.out.println(mv.get()); //或者 FutureTask<String> mv1=new FutureTask<String>(new test2());这边展示了两种方式,一种是利用匿名内部类来实现,另一种是创建一个新类来实现Callable接口,本质上都是将一个实现Callable接口的类传入FutureTask类中,然后再将futureTask对象作为Thread的形参传入。调用start启动线程。
以上实现可以推断出FutureTask类实现了Runable接口,并在重写的run方法中调用重写的cal
l()方法。
其次需要注意FutureTask类的get方法 分为两种情况
- 运行的线程已经结束了,那么可以直接取到运行的结果;
- 运行的线程还没有执行结束,此时主线程会阻塞,直到能取得运行结果。
-
匿名内部类
new Thread(new Runnable() { @Override public void run() { System.out.println("匿名内部类"); } }).start(); new Thread(()->{ System.out.println("匿名内部类实现2"); }).start(); -
线程池
Executor threadPool= Executors.newFixedThreadPool(5); for (int i = 0; i < 5; i++) { threadPool.execute(new Runnable() { @Override public void run() { System.out.println("线程池1"); } }); }
分析以上的创建方式
- java是单继承类型,实现Thread类之后无法继承其他类相对来说没有接口灵活,其次继承一个类,使得继承的类拥有许多额外的方法以及变量,如果不是必需,则没有必要实现Thread类
- 其次实现Runnable接口,可以作为的类可以作为形参传入多个线程中,代码能够被多个线程共享,但是数据是独立的。
- 实现 Callable 接口要实现 call() 方法,并且线程执行完毕后会有返回值。其他的都是重写 run() 方法,没有返回值。
- 使用匿名内部类,内部实现还是传入Runnable接口的重写run方法的类本质没有不同线程池下次分析 todo
看一个例子
public class CreatingThread09 {
public static void main(String[] args) {
new Thread(()-> {
System.out.println("Runnable: " + Thread.currentThread().getName());
}) {
@Override
public void run() {
System.out.println("Thread: " + getName());
}
}.start();
}
}
这个时候可以查看一下Thread源码进行分析
public class Thread implements Runnable {
// Thread维护了一个Runnable的实例
private Runnable target;
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
// ...
// 构造方法传进来的Runnable会赋值给target
this.target = target;
// ...
}
@Override
public void run() {
// Thread默认的run()方法,如果target不为空,会执行target的run()方法
if (target != null) {
target.run();
}
}
}
看到这里是不是豁然开朗呢?既然上面的例子同时继承Thread并实现了Runnable接口,根据源码,实际上相当于重写了Thread的run()方法,在Thread的run()方法时实际上跟target都没有关系了。