解锁性能:玩转多线程编程的新姿势......

拥有多线程和拥有一百枚核弹没有区别,因为都是毁灭性的存在。——麦克阿瑟

在Java中,实现多线程主要有三种方式:继承Thread类、实现Runnable接口和实现Callable接口。

多线程的形式上实现方式主要有两种,一种是继承Thread类,一种是实现Runnable接口。本质上实现方式都是来实现线程任务,然后启动线程执行线程任务(这里的线程任务实际上就是run方法)。

第一种方式:继承Thread类

万物皆可视为对象,线程也不例外。线程作为对象,具备可抽取的公共特性,这些特性可封装为类。通过使用类,我们可以实例化多个相同特性的对象。Thread类是JDK提供的一种简单方式来实现线程,通过继承Thread类并重写其run方法,我们可以在线程启动时执行自定义的run方法体内容。

在Spring Boot项目中,新创建一个TestThread测试线程类,并继承Thread,然后添加一个当前线程的名称,代码如下:

   public TestThread() {
        this.setName("测试线程");
    }

接着重写run方法,来实现相关的业务逻辑,代码如下:

    @Override
    public void run() {
        while (true) {
            System.out.println("线程名:" + Thread.currentThread().getName());
            try {
                //线程休眠2秒
                Thread.sleep(2000);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

然后在项目启动类中main方法中,启动该线程,在启动线程的时候,并不是调用线程类的run方法,而是调用了线程类的start方法。

    public static void main(String[] args) {
        SpringApplication.run(DemoThreadApplication.class, args);
        TestThread thread = new TestThread();
        //使用start,启动线程
        thread.start();
        while (true) {
            System.out.println("线程名:" + Thread.currentThread().getName());
            //线程休眠2秒
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }

启动项目,然后在控制台会输出运行的结果:

线程名:main
线程名:测试线程
线程名:测试线程
线程名:main
线程名:测试线程
线程名:main
线程名:测试线程
线程名:main

主线程main和测试线程run方法交替执行,在控制台是随机输出的,原因就是cpu将时间片分给不同的线程,线程获得时间片后就执行任务,所以这些线程在交替的执行输出,导致输出呈现乱序的效果。由此可见,线程开启后不一定会立即执行,则是由cpu调度执行的。

第二种方式:实现Runnable接口

为了避免Java单继承的限制,我们可以选择实现Runnable接口。通过实现Runnable接口的run()方法,我们可以将一个实现了该接口的对象传递给Thread类的构造方法来创建和启动线程。

Runnable接口的源代码如下:

@FunctionalInterface
public interface Runnable {
	public abstract void run();
}

使用Runnable创建线程的步骤如下:

  1. 定义一个类实现Runnable的接口,作为线程任务类。
  2. 重写run方法,并实现相应的业务代码,也是线程所要执行的代码。
  3. 在启动类的main方法中创建线程任务类。
  4. 创建Thread类,并将线程任务类作为Thread类的构造方法传入,并启动线程。

创建线程任务,每隔2秒执行一次,代码如下:

public class TestRunnable implements Runnable{

    @Override
    public void run() {
        while (true) {
            System.out.println("TestRunnable线程名:" + Thread.currentThread().getName());
            try {
                //线程休眠2秒
                Thread.sleep(2000);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
}

然后在启动类中创建线程,并将任务交付给线程进行处理,然后启动该线程,代码如下:

public static void main(String[] args) {
	TestRunnable testRunnable = new TestRunnable();
        //创建线程对象,通过线程对象来开启的线程
        new Thread(testRunnable).start();
        while (true) {
            System.out.println("线程名:" + Thread.currentThread().getName());
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
}

运行结果如下所示:

线程名:main
TestRunnable线程名:Thread-4
TestRunnable线程名:Thread-4
线程名:main
TestRunnable线程名:Thread-4
线程名:main

实现Runnable接口的输出结果和继承Thread类的结果是一样的,都是交替运行输出, 但是ava是单继承的,就比如我们一个类已经继承了其他的类,就不能使用继承Thread类来创建线程了。但是我们可以使用实现Runnable接口来创建线程。

第三种方式:实现Callable接口

Callable是Java中的一个接口,类似于Runnable,它允许定义一个可以有返回值的任务,并且这个任务可以并发执行。Callable接口和Runnable接口类似,但是它允许返回结果,并能抛出异常。Callable需要依赖FutureTask,用于接收运算结果。一个产生结果,一个拿到结果。FutureTask是Future接口的实现类,也可以用作闭锁。

在使用Callable接口时,需要注意以下几点:

  1. Callable接口的任务可以抛出异常,因此需要在任务中处理异常或者在调用Future.get()方法时处理异常。
  2. Future.get()方法是阻塞的,它会等待任务执行完成并返回结果。如果任务执行时间较长,会影响程序的性能。因此,在使用Future.get()方法时需要谨慎考虑程序的性能和效率。
  3. Callable接口的任务可以并发执行,因此可以在多线程环境下使用,可以使用ExecutorService类来管理线程池并提交任务。

使用Callable创建线程的步骤:

  1. 创建一个类实现Callable接口,并实现call方法。
  2. 创建一个FutureTask,指定Callable对象,做为线程任务。
  3. 创建一个线程,并指定线程任务。
  4. 启动线程。

创建一个TestCallable类,然后实现Callable接口,并实现call方法,代码如下:

public class TestCallable implements Callable {

    @Override
    public Integer call() throws Exception {
        System.out.println("开始执行Callable线程。。。");
        // 睡1s
        Thread.sleep(1000);
        return 111;
    }

}

然后在启动类中执行该线程。

public static void main(String[] args) throws ExecutionException, InterruptedException {
        TestCallable testCallable = new TestCallable();
        //执行Callable方式,需要FutureTask实现类的支持,
        FutureTask<Integer> futureTask = new FutureTask(testCallable);
        //启动线程
        new Thread(futureTask).start();
        System.out.println("接收线程运算的结果。。。");
        Integer result = futureTask.get();
        System.out.println("线程返回值:" + result);
}

运行结果如下所示:

接收线程运算的结果。。。
开始执行Callable线程。。。
线程返回值:111

三种创建线程的方式比较

  1. Thread
    优点:简单易用,易于理解。可以重写Thread类的run()方法来实现线程的功能,符合面向对象的思想。
    缺点
  • Java不支持多重继承,因此如果已经继承了其他类,则无法再继承Thread类。
  • 每个线程都需要创建新的对象,会消耗更多的内存。
  • 不适合在多线程共享资源的情况下使用,因为不同线程的run()方法内部使用的不是同一个对象,需要额外处理共享资源的问题。
  1. 实现Runnable接口
    优点
  • 可以避免Java单继承的限制,可以与其他类继承。
  • 适合在多线程共享资源的情况下使用,因为多个线程可以共享同一个Runnable对象。
  • 可以将任务代码和资源分离,代码更加清晰。
    缺点
  • 不支持传递参数,如果需要传递参数,需要在Runnable对象中添加属性或者使用外部变量。
  • 不适合在需要返回值的情况下使用,因为Runnable接口没有定义返回值的方法。
  1. 实现Callable接口
    优点
  • 可以定义有返回值的任务,比Runnable更加灵活。
  • 可以抛出异常,适合在需要处理异常的情况下使用。
  • 可以使用Future对象获取任务的结果,比使用Thread.join()方法更加方便。
  • 支持并发执行,可以提高程序的效率和性能。
    缺点
  • 使用比较复杂,需要使用ExecutorService类和Future对象来管理线程和获取任务结果。
  • 不适合在需要大量使用线程并且需要返回结果的场景下使用,因为每个任务都需要创建一个新的Callable对象和Future对象,会消耗更多的内存。

综上所述,应该根据具体的需求和场景选择最合适的方式。如果只需要简单的启动线程并且不需要返回结果,可以选择继承Thread类的方式;如果需要传递参数并且不需要返回结果,可以选择实现Runnable接口的方式;如果需要返回结果并且能够处理异常,可以选择实现Callable接口的方式。

代码地址:https://github.com/dawandou/msy-code中的demo-thread项目。

你可能感兴趣的:(Java,java,python,算法)