创建线程有哪几种方式?
2 个回答
鸟随鸾凤飞腾远,人伴贤良品自高。无邪#### 第一种 继承Thread类,重写Run方法 这种方法就是通过自定义CustomThread类继承Thread类,重写run()方法,然后创建CustomThread的对象,然后调用start()方法,JVM会创建出一个新线程,并且为线程创建方法调用栈和程序计数器,此时线程处于就绪状态,当线程获取CPU时间片后,线程会进入到运行状态,会去调用run()方法。并且创建CustomThread类的对象的线程(这里的例子中是主线程)与调用run()方法的线程之间是并发的,也就是在执行run()方法时,主线程可以去执行其他操作。 ```java class CustomThread extends Thread { public static void main(String[] args) { System.out.println(Thread.currentThread().getName()+"线程调用了main方法"); for (int i = 0; i < 10; i++) { if (i == 1) { CustomThread customThread = new CustomThread(); customThread.start(); System.out.println(Thread.currentThread().getName()+"线程--i是"+i); } } System.out.println("main()方法执行完毕!"); } void run() { System.out.println(Thread.currentThread().getName()+"线程调用了run()方法"); for (int j = 0; j < 5; j++) { System.out.println(Thread.currentThread().getName()+"线程--j是"+j); } System.out.println("run()方法执行完毕!"); } } ``` 输出结果如下: ```java main线程调用了main方法 Thread-0线程调用了run()方法 Thread-0线程--j是0 main线程--i是1 Thread-0线程--j是2 Thread-0线程--j是3 Thread-0线程--j是4 run()方法执行完毕! main()方法执行完毕! ``` 可以看到在创建一个CustomThread对象,调用start()方法后,Thread-0调用了run方法,进行for循环,对j进行打印,与此同时,main线程并没有被阻塞,而是继续执行for循环,对i进行打印。 执行原理 首先我们可以来看看start的源码,首先会判断threadStatus是否为0,如果不为0会抛出异常。然后会将当前对象添加到线程组,最后调用start0方法,因为是native方法,看不到源码,根据上面的执行结果来看,JVM新建了一个线程调用了run方法。 ```java private native void start0(); public synchronized void start() { //判断当前Thread对象是否是新建态,否则抛出异常 if (threadStatus != 0) throw new IllegalThreadStateException(); //将当前对象添加到线程组 group.add(this); boolean started = false; try { start0();//这是一个native方法,调用后JVM会新建一个线程来调用run方法 started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } ``` 扩展问题:多次调用Thread对象的start()方法会怎么样? 会抛出IllegalThreadStateException异常。其实在Thread#start()方法里面的的注释中有提到,多次调用start()方法是非法的,所以在上面的start()方法源码中一开始就是对threadStatus进行判断,不为0就会抛出IllegalThreadStateException异常。  **注意事项:** start()方法中判断threadStatus是否为0,是判断当前线程是否新建态,0是代表新建态(上图中的源码注释里面有提到),而不是就绪态,因为Java的Thread类中,Thread的Runnable状态包括了线程的就绪态和运行态,(Thread的state为RUNNABLE时(也就是threadStatus为4时),代表线程为就绪态或运行态)。执行start()方法的线程还不是JVM新建的线程,所以不是就绪态。有一些技术文章把这里弄错了,例如这一篇[深入浅出线程Thread类的start()方法和run()方法](https://juejin.cn/post/6844903612989046797)  **总结** 这种方式的缺点很明显,就是需要继承Thread类,而且实际上我们的需求可能仅仅是希望某些操作被一个其他的线程来执行,所以有了第二种方法。 ### 第二种 实现Runnable接口 这种方式就是创建一个类(例如下面代码中的Target类),实现Runnable接口的Run方法,然后将Target类的实例对象作为Thread的构造器入参target,实际的线程对象还是Thread实例,只不过线程Thread与线程执行体(Target类的run方法)分离了,耦合度更低一些。 ```java class ThreadTarget implements Runnable { void run() { System.out.println(Thread.currentThread().getName()+"线程执行了run方法"); } public static void main(String[] args) { System.out.println(Thread.currentThread().getName()+"线程执行了main方法"); ThreadTarget target = new ThreadTarget(); Thread thread = new Thread(target); thread.start(); } } ``` 输出结果如下:  **原理** 之所以有这种实现方法,是因为Thread类的run()方法中会判断成员变量target是否为空,不为空就会调用target类的run方法。 ```java private Runnable target; public void run() { if (target != null) { target.run(); } } ``` **另外一种写法** 这种实现方式也有其他的写法,可以不创建Target类。 **匿名内部类** 可以不创建Target类,可以使用匿名内部类的方式来实现,因此上面的代码也可以按以下方式写: ```java Thread thread = new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"线程执行了run方法"); } }); thread.start(); ``` **Lamda表达式** 在Java8之后,使用了@FunctionalInterface注解来修饰Runnable接口,表明Runnable接口是一个函数式接口,有且只有一个抽象方法,可以Lambda方式来创建Runnable对象,比使用匿名类的方式更加简洁一些。 ```java @FunctionalInterface public interface Runnable { public abstract void run(); } ``` 因此上面的代码也可以按以下方式写: ```java Thread thread = new Thread(()->{ System.out.println(Thread.currentThread().getName()+"线程执行了run方法"); }) thread.start() ``` **总结** 这种写法不用继承Thread,但是同样也有缺点,就是线程方法体(也就是run方法)不能设置返回值。 ### 第三种 实现Callable接口 Runnable接口中的run()方法是没有返回值,如果我们需要执行的任务带返回值就不能使用Runnable接口。创建一个类CallableTarget,实现Callable接口,实现带有返回值的call()方法,然后根据CallableTarget创建一个任务FutureTask,然后根据FutureTask来创建一个线程Thread,调用Thread的start方法可以执行任务。 ```java public class CallableTarget implements Callable<Integer> { public Integer call() throws InterruptedException { System.out.println(Thread.currentThread().getName()+"线程执行了call方法"); Thread.sleep(5000); return 1; } public static void main(String[] args) throws ExecutionException, InterruptedException { System.out.println(Thread.currentThread().getName()+"线程执行了main方法"); CallableTarget callableTarget = new CallableTarget(); FutureTask<Integer> task = new FutureTask<Integer>(callableTarget); Thread thread = new Thread(task); thread.start(); Integer result = task.get();//当前线程会阻塞,一直等到结果返回。 System.out.println("执行完毕,打印result="+result); System.out.println("执行完毕"); } } ``` 原理就是Thread类默认的run()方法实现是会去调用自身实例变量target的run()方法,(target就是我们构造Thread传入的FutureTask),而FutureTask的run方法中就会调用Callable接口的实例的call()方法。 ```java //Thread类的run方法实现 @Override public void run() { if (target != null) { //这里target就是我们在创建Thread时传入的FutureTask实例变量 target.run(); } } //FutureTask类的run方法实现 public void run() { if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) return; try { Callable<V> c = callable; if (c != null && state == NEW) { V result; boolean ran; try { //在这里会调用Callable实例的call方法 result = c.call(); ran = true; } catch (Throwable ex) { result = null; ran = false; setException(ex); } if (ran) set(result); } } finally { // runner must be non-null until state is settled to // prevent concurrent calls to run() runner = null; // state must be re-read after nulling runner to prevent // leaked interrupts int s = state; if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); } } ```发布于 2021-11-29 14:47 •IP属地 辽宁举报评论收藏
入体冰凉的月光,都比不上你绝情的目光。松林①. 继承Thread类创建线程类 定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务。因此把run()方法称为执行体。 创建Thread子类的实例,即创建了线程对象。 调用线程对象的start()方法来启动该线程。 ②. 通过Runnable接口创建线程类 定义runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。 创建 Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。 调用线程对象的start()方法来启动该线程。 ③. 通过Callable和Future创建线程 创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。 创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。 使用FutureTask对象作为Thread对象的target创建并启动新线程。 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。发布于 2021-07-01 19:40 •IP属地 云南举报评论收藏
公众号进入网站体验更佳

