Java 实现多线程的四种方式

x33g5p2x  于2021-12-18 转载在 其他  
字(6.5k)|赞(0)|评价(0)|浏览(430)

在 Java 中实现多线程一共有四种方式:

  1. 继承 Thread 类
  2. 实现 Runnable 接口
  3. 实现 Callable 接口
  4. 线程池

下面我将对这四种方式进行入门级的解析和演示。

一、继承 Thread 类

通过继承 Thread 类实现多线程的步骤如下:

  1. 创建 MyThread 类,让其继承 Thread 类并重写 run() 方法。
  2. 创建 MyThread 类的实例对象,即创建一个新线程。
  3. 调用 start() 方法,启动线程。

代码示例如下:

public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("我是通过继承 Thread 类创建的多线程,我叫" + Thread.currentThread().getName());
    }
}

class TestMyThread {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        myThread1.setName("Thread-1");
        MyThread myThread2 = new MyThread();
        myThread2.setName("Thread-2");
        MyThread myThread3 = new MyThread();
        myThread3.setName("Thread-3");

        myThread1.start();
        myThread2.start();
        myThread3.start();
    }
}

为了演示线程执行顺序的随机性,我特意创建了三个线程,并为每一个线程命名,下面是我运行五次程序的执行结果:

// 第一次
我是通过继承 Thread 类创建的多线程,我叫Thread-2
我是通过继承 Thread 类创建的多线程,我叫Thread-1
我是通过继承 Thread 类创建的多线程,我叫Thread-3

// 第二次
我是通过继承 Thread 类创建的多线程,我叫Thread-1
我是通过继承 Thread 类创建的多线程,我叫Thread-3
我是通过继承 Thread 类创建的多线程,我叫Thread-2

// 第三次
我是通过继承 Thread 类创建的多线程,我叫Thread-1
我是通过继承 Thread 类创建的多线程,我叫Thread-3
我是通过继承 Thread 类创建的多线程,我叫Thread-2

// 第四次
我是通过继承 Thread 类创建的多线程,我叫Thread-1
我是通过继承 Thread 类创建的多线程,我叫Thread-2
我是通过继承 Thread 类创建的多线程,我叫Thread-3

// 第五次
我是通过继承 Thread 类创建的多线程,我叫Thread-2
我是通过继承 Thread 类创建的多线程,我叫Thread-1
我是通过继承 Thread 类创建的多线程,我叫Thread-3

从上面的执行结果我们可以看到线程的执行顺序和代码中编写的顺序没有关系,线程的执行顺序是具有随机性的。

二、实现 Runnable 接口

Runnable 接口只有一个 run() 方法,源码如下:

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

通过实现 Runnable 接口实现多线程的步骤如下:

  1. 创建 MyRunnable 类实现 Runnable 接口。
  2. 创建 MyRunnable 类的实例对象 myRunnable 。
  3. 把实例对象 myRunnable 作为参数来创建 Thread 类的实例对象 thread,实例对象 thread 就是一个新线程。
  4. 调用 start() 方法,启动线程。

代码示例如下:

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        System.out.println("我是通过实现 Runnable 接口创建的多线程,我叫" + Thread.currentThread().getName());
    }
}

class TestMyRunnable {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
    }
}

执行结果如下:

我是通过实现 Runnable 接口创建的多线程,我叫Thread-0

相比于继承 Thread 类的方法来说,实现 Runnable 接口是一个更好地选择,因为 Java 不支持多继承,但是可以实现多个接口。

有一点值得注意的是 Thread 类也实现了 Runnable 接口,这意味着构造函数 Thread(Runnable target) 不仅可以传入 Runnable 接口的对象,而且可以传入一个 Thread 类的对象,这样就可以将一个 Thread 对象中的 run() 方法交由其他线程进行调用。

三、实现 Callable 接口

Callable 接口只有一个 call() 方法,源码如下:

public interface Callable<V> {
    V call() throws Exception;
}

从源码我们可以看到 Callable 接口和 Runnable 接口类似,它们之间的区别在于 run() 方法没有返回值,而 call() 方法是有返回值的。

通过实现 Callable 接口实现多线程的步骤如下:

  1. 创建 MyCallable 类实现 Callable 接口。
  2. 创建 MyCallable 类的实例对象 myCallable。
  3. 把实例对象 myCallable 作为参数来创建 FutureTask 类的实例对象 futureTask。
  4. 把实例对象 futureTask 作为参数来创建 Thread 类的实例对象 thread,实例对象 thread 就是一个新线程。
  5. 调用 start() 方法,启动线程。

代码示例如下:

public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int a = 6;
        int b = 9;
        System.out.println("我是通过实现 Callable 接口创建的多线程,我叫" + Thread.currentThread().getName());
        return a + b;
    }
}

class TestMyCallable {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable myCallable = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask(myCallable);
        Thread thread = new Thread(futureTask);
        thread.start();
        System.out.println("返回值为:" + futureTask.get());
    }
}

执行结果如下:

我是通过实现 Callable 接口创建的多线程,我叫Thread-0
返回值为:15

FutureTask 类提供了一个 get() 方法用来获取 call() 方法的返回值,但需要注意的是调用这个方法会导致程序阻塞,必须要等到线程结束后才会得到返回值。

四、线程池

在 Java 中构建一个新的线程是需要一定的系统开销的,前面三种实现多线程的方法在线程执行完任务后就会将线程销毁,那么是否可以在线程执行完任务后将线程保存下来,给下一个任务使用呢?答案是可以的,为了解决这个问题,线程池应运而生。

顾名思义,线程池就是用来存储线程的池子。线程池中包含许多准备运行的线程,我们只需要为线程池提供一个个任务,线程池就会按照一定的规则去调用这些任务,当一个任务完成后,调用这个任务的线程不会死亡,而是留在线程池中准备为下一个任务提供服务。

Executors 类提供了许多静态工厂方法用来构造线程池,这里我介绍其中的三种:

  1. newFixedThreadPool(int nThreads)

该方法用来构造一个固定大小的线程池,空闲的线程会一直保留着,如果提交的任务数多于空闲线程数,就会把未得到服务的任务放到队列中等待。

代码示例如下:

public class MyFixedThreadPool {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        // 调用 Runnable 任务
        TestRunnable1 testRunnable1 = new TestRunnable1();
        for (int i = 0; i < 5; i++) {
            // 调用 Runnable 任务可以用以下两种方法,二者的区别在于前者没返回值,后者有返回值
            executorService.execute(testRunnable1);
            Future<?> submit = executorService.submit(testRunnable1);
        }
        // 调用 Callable 任务
        TestCallable1 testCallable1 = new TestCallable1();
        for (int i = 0; i < 5; i++) {
            // 调用 Callable 任务只能用这一种方法
            Future<Integer> submit = executorService.submit(testCallable1);
            System.out.println("返回值:" + submit.get());
        }
    }
}

class TestRunnable1 implements Runnable {
    @Override
    public void run() {
        System.out.println("我是 Runnable 任务,调用我的线程是:" + Thread.currentThread().getName());
    }
}

class TestCallable1 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("我是 Callable 任务,调用我的线程是:" + Thread.currentThread().getName());
        return 666;
    }
}
  1. newCachedThreadPool()

该方法构建的线程池会立即执行任务,如果当前存在空闲线程,则直接执行任务;如果当前不存在空闲线程,则创建一个新线程执行任务。在该线程池内的空闲线程只会保留 60 秒。

public class MyCachedThreadPool {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newCachedThreadPool();
        // 调用 Runnable 任务
        TestRunnable2 testRunnable2 = new TestRunnable2();
        for (int i = 0; i < 5; i++) {
            // 调用 Runnable 任务可以用以下两种方法,二者的区别在于前者没返回值,后者有返回值
            executorService.execute(testRunnable2);
            Future<?> submit = executorService.submit(testRunnable2);
        }
        // 调用 Callable 任务
        TestCallable2 testCallable2 = new TestCallable2();
        for (int i = 0; i < 5; i++) {
            // 调用 Callable 任务只能用这一种方法
            Future<Integer> submit = executorService.submit(testCallable2);
            System.out.println("返回值:" + submit.get());
        }
    }
}

class TestRunnable2 implements Runnable {
    @Override
    public void run() {
        System.out.println("我是 Runnable 任务,调用我的线程是:" + Thread.currentThread().getName());
    }
}

class TestCallable2 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("我是 Callable 任务,调用我的线程是:" + Thread.currentThread().getName());
        return 666;
    }
}
  1. newSingleThreadExecutor()

该方法构建的线程池只存在一个线程,会顺序地执行所提交的任务。

public class MySingleThreadExecutor {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        // 调用 Runnable 任务
        TestRunnable3 testRunnable3 = new TestRunnable3();
        for (int i = 0; i < 5; i++) {
            // 调用 Runnable 任务可以用以下两种方法,二者的区别在于前者没返回值,后者有返回值
            executorService.execute(testRunnable3);
            Future<?> submit = executorService.submit(testRunnable3);
        }
        // 调用 Callable 任务
        TestCallable3 testCallable3 = new TestCallable3();
        for (int i = 0; i < 5; i++) {
            // 调用 Callable 任务只能用这一种方法
            Future<Integer> submit = executorService.submit(testCallable3);
            System.out.println("返回值:" + submit.get());
        }
    }
}

class TestRunnable3 implements Runnable {
    @Override
    public void run() {
        System.out.println("我是 Runnable 任务,调用我的线程是:" + Thread.currentThread().getName());
    }
}

class TestCallable3 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("我是 Callable 任务,调用我的线程是:" + Thread.currentThread().getName());
        return 666;
    }
}

相关文章