天天看点

Java并发编程之创建和启动线程

创建线程的正确方法:2种

  • 实现Runnable接口创建线程
/**
 * 实现Runnable接口创建线程
 */
public class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println("实现Runnable接口创建线程");
    }

    public static void main(String[] args) {
        new Thread(new MyThread()).start();
    }
}
           
  • 继承Thread类创建线程
/**
 * 继承Thread类创建线程
 */
public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("继承Thread类创建线程");
    }

    public static void main(String[] args) {
        new Thread(new MyThread()).start();
    }
}
           

实现Runnable接口创建线程的方法更好

  1. 解耦:具体执行的任务,也就是run方法应该与线程的创建、运行、销毁是不相关的;
  2. 资源节约:如果用继承Thread这种方法,每次新建个任务只能新建个线程,线程的创建、运行和销毁的消耗是比较大的;如果使用实现Runnable接口的方法,后续我们可以使用线程池等工具,不用再去新建线程,带来的损耗会大大降低;
  3. 扩展性:Java是不支持多继承的,如果采用继承Thread类的方法,那么子类就无法再去继承其他类,大大的限制了子类的扩展性

两种方法的本质对比

让我们看看Thread类里面的run()是怎么写的:

@Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
           
  • 代码非常的简单,意思是有Runnable类型的target对象,那么就执行target对象里面的run();
  • 使用实现Runnable接口方法时,因为传入了Runnable对象,所以新建线程对象里面执行了target.run();
  • 使用继承Thread类方法时,因为子类重写了Thread类的run(),所以最终执行子类的run();

同时使用两种方法会怎么样?

public class MyThread {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("实现Runnable接口创建线程");
            }
        }) {
            @Override
            public void run() {
                System.out.println("继承Thread类创建线程");
            }
        }.start();
    }
}
           
继承Thread类创建线程
           

上述代码虽然传入了Runnable对象,但是子类重写了Thread类的run(),导致该方法不再调用target.run(),所以同时使用两种方法,只有继承Thread类方法才生效!

最精准的描述

  • 通常可以分为两类:实现Runnable接口、继承Thread类重写run()
  • 准确的讲,创建线程只有一种方式:创建Thread对象,而在Thread类里面,实现run()有两种不同的情况:
    1. 实现Runnable接口,并把实例对象传给Thread类去执行run();
    2. 继承Thread类,直接重写了Thread类的run();

典型的错误观点

  • “线程池创建线程”
public class MyThread {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 100; i++) {
            executorService.submit(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(500);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName());
                }
            });
        }
    }
}
           
pool-1-thread-14
pool-1-thread-9
pool-1-thread-8
pool-1-thread-13
pool-1-thread-15
pool-1-thread-16
......
           

结果表明,线程池确实创建了新的线程,但是这只是表现,其内部是通过实现ThreadFactory接口来创建线程的,如下所示:

public interface ThreadFactory {

    /**
     * Constructs a new {@code Thread}.  Implementations may also initialize
     * priority, name, daemon status, {@code ThreadGroup}, etc.
     *
     * @param r a runnable to be executed by new thread instance
     * @return constructed thread, or {@code null} if the request to
     *         create a thread is rejected
     */
    Thread newThread(Runnable r);
}
           
  • “通过Callable和FutureTask创建线程,也算一种新的创建线程的方式”
public class CallableAndFutureTask {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask futureTask = new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {
                return Thread.currentThread().getName();
            }
        });
        new Thread(futureTask).start();
        System.out.println(futureTask.get());
    }
}
           

实现Callable接口并创建FutureTask对象的方法确实也可以创建线程,但是和线程池一样,这只是对创建Thread对象的一种包装,如下图所示:

public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}
           

FutureTask实现了RunnableFuture接口,而RunnableFuture继承与Runnable接口,根据这种传递关系,相当于FutureTask实现了Runnable接口,在实现的run()方法中调用了Callable接口的call方法来获取返回值,并把返回值set到result中,最后通过get()获取到返回值,如下图所示:

public void run() {
        .......
          Callable<V> c = callable;
	      result = c.call();
          ran = true;
          if (ran) set(result);
          }
        ......
    }
           

以上两种说法,只是对于实现Runnable接口和继承Thread类方法的封装,本质上不算新的方法!

启动线程的正确和错误方式

public class MyThread {

    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName());
            }
        });
        thread.run();
        thread.start();
    }
}
           
main
Thread-0
           

通过上述代码,我们可以看到:

  1. 执行run(),打印出的是main线程名称,说明执行该方法的是主线程,并没有启动一个新的线程;
  2. 执行start(),打印出的是Thread-0线程名称,说明启动了一个新的线程,并在新线程里执行了run()。

start()源码分析

public synchronized void start() {
        if (threadStatus != 0) throw new IllegalThreadStateException();
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
            }
        }
    }
           
1. 启动新线程检查线程状态,如果线程状态不为0(还没有开始),就会抛出线程状态异常
2. 加入线程组
3. 调用start0()
           

run()源码分析

@Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
           
  1. run()有两种情况,一种情况是重写了Thread类的run方法,另外一种是向Thread类传入Runnable对象
  2. run()是一个普通方法,如果直接执行run(),只会通过当前线程去执行,要想启动新线程去执行run(),需执行start()。

继续阅读