创建线程的四种方式

本文详细介绍了在Java中创建多线程的三种主要方法:继承Thread类、实现Runnable接口和实现Callable接口。通过示例代码展示了每种方法的具体实现,并讨论了它们之间的差异和适用场景。

继承Thread类:

继承Thread类重写run()方法,这个是在初学Java多线程的时候都了解的创建线程的方式。其中每一个继承Thread类的子类都要重写run()方法,run()方法作为每一个线程的核心方法。

  • 第一步:继承Thread类,重写run()方法。
  • 第二步:线程的启动,调用start()方法,将新创建的线程加入到就绪状态。其中run()方法是在操作系统创建完线程后回调的方法,不可以手动调用;手动调用run方法只是执行方法而已,并不是真的启动一个新的线程。
public class ThreadTest extends Thread{

    @Override
    public void run() {
        for (int i = 0; i < 10; i++){
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
    }

    public static void main(String[] args) {
        ThreadTest t1 = new ThreadTest();
        ThreadTest t2 = new ThreadTest();
        t1.setName("thread_t1");
        t2.setName("thread_t2");
        t1.start();
        t2.start();
    }
}

实现Runnable()接口:

在上面的方案中,通过继承Thread类重新run()方法明显存在一些不足,首先我们都知道Java是单继承,使用继承去实现一个多线程是很大的牺牲。这里给出实现接口的方案,实现runnable接口重写run方法。

/**
 * 实现Runnable接口创建线程
 */
public class MyThread implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(100);
                System.out.println("Thread name is " + Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Thread t1 = new Thread(new MyThread(),"Thread_t1");
        Thread t2 = new Thread(new MyThread(),"Thread_t2");
        Thread t3 = new Thread(new MyThread(),"Thread_t3");
        t1.start();
        t2.start();
        t3.start();
    }
}

实现Callable接口:

对于初学多线程的小白,相信这个接触的比较少吧,Callable接口是Java.util.concurrent包下的一个接口,它也声明了一个方法,但是不是run()方法,而是call()方法。
为什么要有Callable接口呢,因为无论是继承Thread类还是实现Runnable接口都是没有办法获得线程的返回值的,但是实现Callable接口创建线程是可以获得线程的返回值的。
下面就是Callable接口:

public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

那么要怎么使用Callable接口呢?一般情况下是配合ExecutorService一起使用的,在ExecutoeService接口中声明类若干个Submit方法的重载版本:

<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

如上代码片段是ExecutorService接口中定义的方法,可以看到第一一个Submit方法的参数中类型是Callable,这里只需要了解Callable是和ExecutorService配合使用的就可以了,这里使用比较多的submit方法是第一个和第三个。

Future

在Callable介绍中可以看到,每一个submit的返回类型都和Future有关,那么Future又是什么呢?
Future就是对于具体的Runnable或者Callable任务执行结果进行取消、查询是否完成、获取结果、必要时可以通过get获取结果。

public interface Future<V> {
	boolean cancel(boolean mayInterruptIfRunning);
	boolean isCancelled();
	boolean isDone();
	V get() throws InterruptedException, ExecutionException;
	V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

在Future中声明了5个方法,下面依次解释一下:

  • cancel()方法是用来取消任务的,任务取消成功返回true,失败返回false。mayInterruptIfRunning参数表示是否允许取消正在执行的却没有完毕的任务,如果设置为true则表示取消正在执行过程中的任务。如果任务已经完成,无论mayInterruptIfRunning为true还是false都无法取消任务。
  • isCancelled():表示任务是否取消成功,如果在任务正常完成之前取消成功则返回true;
  • isDone():表示任务是否已经完成,true表示完成。
  • get()方法用于获取线程执行结果,这个方法会产生阻塞,会一直等待任务执行完毕才返回。
  • V get(long timeout, TimeUnit unit):用来获取线程执行结果,如果在指
    定时间内,还没有返回结果,直接返回null。

这里可以看出Future提供了三种功能:

  1. 判断任务是否完成。
  2. 能够中断任务。
  3. 能够获取任务的返回结果。
    是为Future只是一个接口,在具体使用的时候还是要使用实现类FutureTask。

FutureTask

这里先来看一下FutureTask的实现:

public class FutureTask<V> implements RunnableFuture<V>

FutureTask提供了两个构造器:

public FutureTask(Callable<V> callable) {
}
public FutureTask(Runnable runnable, V result) {
}

其中FutureTask是Future的实现类,也是唯一一个实现类。

Callable + Future使用示例:

/**
 * 实现Callable接口创建线程
 */
public class CallableTest {

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newCachedThreadPool();
        Task task = new Task();
        Future<Integer> result = executorService.submit(task);
        executorService.shutdown();

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("主线程正在执行任务");

        try {
            System.out.println("子线程执行结果: " + result.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }

        System.out.println("所有任务执行完毕");
    }

    static class Task implements Callable<Integer> {

        @Override
        public Integer call() throws Exception {
            System.out.println("子线程在进行计算");
            Thread.sleep(2000);
            int sum = 0;
            for (int i = 0; i < 100; i++) {
                sum += i;
            }
            return sum;
        }
    }
}

在这里插入图片描述
如上面代码:

  • 首先使用创建了一个Callable的任务Task。
  • 将任务提交到ExecutorService线程池。
  • 使用Future get()阻塞线程,获取子线程的返回结果。

通过线程池启动多线程:

使用Executor的工具类可以创建三种类型的普通线程池:

FixThreadPool(int n); 固定大小线程池

/**
 * 使用FixThreadPool(int n)固定大小的线程池
 */
public class ThreadPool1 {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newFixedThreadPool(5);
        for (int i = 0; i < 5; i++) {
            executor.submit(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10; j++) {
                        System.out.println(Thread.currentThread().getName() + " " + j);
                    }
                }
            });
        }
        executor.shutdown();
    }
}

SingleThreadPool:单线程池

需要保证顺序执行的任务场景:

/**
 * SingleThreadPool但线程池
 */
public class ThreadPool2 {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5; i++) {
            executor.submit(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10; j++) {
                        System.out.println(Thread.currentThread().getName() + " " + j);
                    }
                }
            });
        }
        executor.shutdown();
    }
}

CachedThreadPool;缓存线程池

当提交任务速度高于线程池中任务的处理速度时,缓存线程池会不断的创建线程,适用于提交短期异步小程序,以及负载较轻的服务器;

/**
 * CachedThreadPool 缓存线程池
 */
public class ThreadPool3 {
    public static void main(String[] args) {
        ExecutorService executor = Executors.newCachedThreadPool();
        for (int i = 0; i < 5; i++) {
            executor.submit(new Runnable() {
                @Override
                public void run() {
                    for (int j = 0; j < 10; j++) {
                        System.out.println(Thread.currentThread().getName() + " " + j);
                    }
                }
            });
        }
        executor.shutdown();
    }
}

上面只是给出了四种创建线程的方式和案例,具体的一些细节和原理在其他文章中会给出。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值