Java中线程的3种创建方式对比及常用方法介绍

Java中线程的3种创建方式:

  1. 继承Thread类
    创建自定义线程类并继承Thread类–>重写run()–>实例化自定义线程类–>调用自定义实现类的start()启动线程
public class ThreadDemo extends Thread {
    @Override//重写run()
    public void run() {
        System.out.println(Thread.currentThread().getName()+" subthread");
    }
}
public class App {
    public static void main(String[] args) {
       System.out.println(Thread.currentThread().getName()+" main");//打印当前线程(主线程)的线程名
        ThreadDemo threadDemo=new ThreadDemo();//实例化自定义的线程类
        threadDemo.start();//开启线程
    }
}

在这里插入图片描述

  1. 实现Runnable接口
    创建自定义线程类并实现Runnable接口–>实现Runnable中的run()–>实例化自定义的线程类–>将创建的实例当作Thread类构造方法的参数来实例化Thread类–>调用Thread类实例的start()方法启动线程
public class RunnableDemo implements Runnable {//创建自定义线程类
    @Override//实现run()
    public void run() {//一个线程所有的操作都要在run()中
        System.out.println(Thread.currentThread().getName()+" subthread");//打印当前线程线程名
    }
}

public class App {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+" main");//打印当前线程(主线程)的线程名
        RunnableDemo runnable = new RunnableDemo();//实例化自定义线程类
        Thread thread=new Thread(runnable);//实例化Thread类
        thread.start();//开启一个线程
    }
}

在这里插入图片描述

  1. 实现Callable接口
    创建自定义线程类并实现Callable类–>实现call()方法–>实例化自定义线程类–>将自定义线程类的实例当作参数来实例化FutureTask类–>将FutureTask类的实例当作Thread类构造方法的参数来实例化Thread类–>调用Thread类实例的start()方法启动线程
import java.util.concurrent.Callable;

public class CallableDemo implements Callable<Integer> {
    @Override//实现call()
    public Integer call() throws Exception {
        int i=100;
        System.out.println(Thread.currentThread().getName()+" subthread");
        return i;//返回当前线程的执行结果
    }
}

import java.util.concurrent.*;

public class App {
    public static void main(String[] args) {
       System.out.println(Thread.currentThread().getName()+" main");//打印当前线程(主线程)的线程名
        CallableDemo callable=new CallableDemo();//实例化自定义线程类
        FutureTask<Integer> result = new FutureTask<>(callable);
        Thread thread = new Thread(result);
        thread.start();
        try {
            System.out.println(result.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}

在这里插入图片描述
三种方法对比:

  1. 继承Thread类为单继承,而实现接口为多实现,所以实现接口方式在使用上比较灵活。
  2. 实现接口创建的线程可以放入线程池来管理,而继承Thread类创建的线程不可以放入线程池。
  3. Thread类底层实现了Runnable接口,底层也实现了run()方法,我们继承Thread类需要重写该方法;而实现Runnable接口需要实现run()方法。可以试一下,当我们继承Thread类时,不重写run()方法不会报错,而实现Runnable接口,不实现run()会报错。
    在这里插入图片描述
  4. 继承Thread类的线程类的实例直接调用start()方法去启动该线程;
    实现Runnable接口的线程类的实例需要作为参数来实例化Thread类,然后再由Thread类实例调用start()去启动线程;
    实现Callable接口的线程类的实例需要作为参数来实例化FutureTask类,然后再把FutureTask类实例作为参数来实例化Thread类,然后再由Thread类实例调用start()去启动线程。
  5. 继承Thread类和实现runnable接口创建的线程的操作都在run()方法中;
    实现Callable接口创建的线程操作都在call()方法中,而且在实现接口时可以指定一个参数类型作为call()返回值的类型,call()方法是一个有返回值而且抛异常的方法。返回值由FutureTask负责获取。

总结:继承Thread类这种方法使用起来比较方便,但底层还是实现Runnable接口;实现Runnable接口的方法启动线程需要用Thread类的start()方法或放在线程池中启动;实现Callable接口的方法也需要用Thread类的start()方法或放在线程池中启动,而且需要先实例化FutureTask,但可以得到线程执行的返回值。

注意:
调用run()/call()只是调用执行该方法,并没有开启线程,还是单线程(主线程)在运行,必须调用start()或者放入线程池才能启动线程,实现多线程并发执行。每个线程的start()只能调用一次!重复调用会报错。
在这里插入图片描述
在这里插入图片描述
一些常用方法:

  • Thread.currentThread().getName() ; 获取当前线程的名称
  • thread.setName(""); 继承Thread类创建的线程可以调用此方法设置线程名称
  • Thread thread=new Thread(runnable,"");实现Runnable接口创建的线程在实例化Thread时可以传一个String型参数来设置线程名
  • Thread thread = new Thread(result,"");实现Callable接口创建的线程在实例化Thread时可以传一个String型参数来设置线程名
  • result.cancel(true);FutureTask中的cancel()用来取消该线程的执行
    在这里插入图片描述
    我在获取结果的代码前加了cancel(),打印结果会报错,因为没有执行就没有结果。
  • Thread.currentThread().getState();获取线程的状态
  • Thread.currentThread().setDaemon(true);设置线程为守护线程(如GC)
  • Thread.sleep(long time);让当前线程阻塞(睡眠)time,单位是ms,后面可以再传一个参数,设置时间单位。会抛interruptedException异常。是静态方法。
  • Thread.yield();让正在执行的线程让步或暂停,让优先级高或相等的线程来执行。(如果没有优先级比当前线程高或相等的线程,或当前只有一个线程在执行,当前线程会立即获取cpu使用权);是一个静态方法,如果不是静态方法,得创建Thread实例才能调用该方法。
  • Thread中的join();让正在执行的线程等待,join()的调用方先执行。可以控制程序的执行顺序。例如:在a线程中,b调用了join(),那就让a线程等b执行完再执行。传时间参数可以设置的等待时间,时间过后以前等待的线程就开始执行,而不是等调用方执行完才开始执行。可以传第二个参数设置时间单位,类似sleep()。会抛InterruptedException异常。
//让ABC三个线程按照BAC的顺序执行
class Thread1 extends Thread{
    private Thread2 thread2;

    public Thread1(Thread2 thread2) {
        this.thread2=thread2;
    }
    @Override
    public void run() {
        try {
            thread2.join();//让A等待,先执行B
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+" 执行A");
    }
}

class Thread2 extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+" 执行B");
    }
}
class Thread3 extends Thread{
    private Thread1 thread1;
    public Thread3(Thread1 thread1) {
        this.thread1=thread1;
    }

    @Override
    public void run() {
        try {
            thread1.join();//让C等待,先执行A
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+" 执行C");
    }
}
public class Test {
    public static void main(String[] args) {
        Thread2 thread2=new Thread2();
        Thread1 thread1=new Thread1(thread2);
        Thread3 thread3=new Thread3(thread1);
        thread1.setName("A");
        thread2.setName("B");
        thread3.setName("C");
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

执行结果:
在这里插入图片描述
不用join()的话,可能每次的执行顺序都不同,可以自己试一下。

  • Thread中的interrupt():中断操作,中断调用该方法的线程。底层调用了一个native方法interrupt0(),这个native方法是修改该线程的中断标志位为中断。
    在这里插入图片描述
    如果作用于运行中的线程,只是修改线程的中断标志位,不会中断线程;
    如果作用于阻塞线程(调用了sleep()、join()),修改中断标志位,并令当前阻塞中的线程感知到标志位做了修改,就会中断阻塞,抛出InterruptedException异常。
  • Thread中的IsInterrupted():判断调用该方法的线程中断标志位是否被修改为中断,返回值为boolean型,true为修改,false为未修改。
  • Thread中的setDaemon():传参为true–>设为守护线程;false–>设为用户线程(默认)。
  • Thread中的isDaemon():判断当前线程是否为守护线程,返回true为守护线程。
    守护线程:脱离于控制终端,提供一些通用服务的线程。如负责垃圾回收的线程(GC)。生命周期依赖于用户线程,当有用户线程存在就会有守护线程。
    用户线程:进入run()到出run()为用户线程的生命周期。
  • Thread中的setPriority(int newPriority):设置线程的优先级(1~10)。默认为5,最小为1,最大为5;数字越大,优先级越高,被优先执行的概率越大;优先级设置的相近(如4、5)可能执行的顺序为4比5早。
    这里设置的优先级只是设置被cpu优先执行的概率,最终执行的先后取决于操作系统。如:设置一个线程的优先级为2,操作系统中可能有其他优先级高于2的本地线程,可能这个优先级为2的线程永远无法执行,为了避免这种情况,操作系统为每个线程设置权重,每次遍历队列发现某线程未执行就给它的权重+1,过一段时间可能这个优先级为2的线程权重变得很大,优先级也就改变了。
  • Thread中的setPriority():获取当前线程的优先级。
发布了19 篇原创文章 · 获赞 9 · 访问量 2212

猜你喜欢

转载自blog.csdn.net/weixin_44480874/article/details/99735403