《Java 多线程》——创建线程

341 阅读3分钟

最近面试经常被问到多线程的一些知识,最近可能会系统的学习一下多线程,然后也会写笔记进行总结。一起学的小伙伴可以可以交流一下

创建线程的几种方式

  • 继承Thread类
  • 实现Runable接口
  • 实现 Callable接口
  • 匿名内部类 使用
  • 线程池

创建线程的本质

以上几种方式本质上还是就两种实现Runnable接口以及 继承Thread类。如果再细致一些就是实现了Runable接口我们可以看到Thread类内部就是实现Runable接口。

下面先看五种方式创建线程,最后看一下Thread线程的源码是如何实现的

  1. 继承Thread类

    class tets3 extends  Thread{
        @Override
        public void run() {
            System.out.println("线程3 启动");
        }
    }
    
    test3 thread=new test3();
    thread.start();
    
  2. 实现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类

  3. 实现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方法 分为两种情况

    • 运行的线程已经结束了,那么可以直接取到运行的结果;
    • 运行的线程还没有执行结束,此时主线程会阻塞,直到能取得运行结果。
  4. 匿名内部类

     new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println("匿名内部类");
                }
            }).start();
    
    
     new Thread(()->{
                System.out.println("匿名内部类实现2");
            }).start();
    
  5. 线程池

    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都没有关系了。