JAVA多线程五种创建方式

6 篇文章 0 订阅
4 篇文章 0 订阅

JAVA多线程五种创建方式

一、继承Thread类

Mythread类

/**
 * @creater keke
 * @time 2021/1/20 16:48
 * @description
 */
public class MyThread extends Thread {
    @Override
    public void run() {
            //通过继承Thread类的方式实现多线程时,可以直接使用this获取当前执行的线程
            System.out.println("线程的名字是"+this.getName());//输出“线程的名字是Thread-0”
        
    }
}

main方法

/**
 * @creater keke
 * @time 2021/1/20 12:44
 * @description
 */
public class MyMain {
    public static void main(String[] args) {

        Thread thread = new MyThread();
        //调用Thread类的currentThread()方法获取当前线程
        System.out.println(Thread.currentThread().getName()); //输出“main”
        //启动第一个线程
        thread.start();

    }
}

二、实现Runnable接口

MyRunnable类

/**
 * @creater keke
 * @time 2021/1/20 12:38
 * @description
 */
public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10 ; i++) {
            //当线程类实现Runnable接口时,获取当前线程对象只能通过Thread.currentThread()获取
            System.out.println(Thread.currentThread().getName() + " " + i);//输出线程1 0-线程1 9
        }
    }
}

main方法

/**
 * @creater keke
 * @time 2021/1/20 12:44
 * @description
 */
public class MyMain {
    public static void main(String[] args) {
        //调用Thread类的currentThread()方法获取当前线程
        System.out.println(Thread.currentThread().getName());//输出”main“
        MyRunnable myRunnable = new MyRunnable();
        
        //通过new Thread(target,name)的方式创建线程:name:为线程设置名字
        new Thread(myRunnable, "线程1").start();
    }
}
  • 实现Runnable接口的类的实例对象作为Thread对象的target,Runnable实现类里包含的run()方法作为线程执行体,实际的线程对象依然是Thread实例,负责执行其target的run()方法;

三、FutureTask

MyCallable实现Callable接口

import java.util.concurrent.Callable;

/**
 * <h3>project2</h3>
 * <p>实现Callable</p>
 *
 * @author : keke
 * @date : 2021-01-20 19:41
 */
public class MyCallable implements Callable<String> {
    @Override
    public String call() {
        System.out.println("通过实现Callable,线程名称:" + Thread.currentThread().getName());
        return "这是个带返回的线程实现";
    }
}
  • Callable接口
    1、Callable接口提供了一个call()方法作为线程执行体,call()方法比run()方法功能要强大:call()方法可以有返回值,可以声明抛出异常。
    2、Callable接口是一个带泛型的接口,泛型的类型就是线程返回值的类型。实现Callable接口中的call()方法,方法的返回类型与泛型的类型相同。

main方法

import java.util.concurrent.*;

/**
 * <h3>project2</h3>
 * <p>main方法</p>
 *
 * @author : keke
 * @date : 2021-01-20 19:21
 */
public class MyMain {
    public static void main(String[] args) throws InterruptedException, TimeoutException, ExecutionException {
        Callable<String> callable=new MyCallable();
        FutureTask<String> futureTask=new FutureTask<String>(callable);
        new Thread(futureTask).start();
/*      task.get()获取call()的返回值。若调用时call()方法未返回,则阻塞线程等待返回值
        get的传入参数为等待时间,超时抛出超时异常;传入参数为空时,则不设超时,一直等待*/
        System.out.println(futureTask.get());
        System.out.println(futureTask.get(10, TimeUnit.MILLISECONDS));
    }


}
  • FutureTask
    1、使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。(FutureTask是一个包装器,它通过接受Callable来创建)。
    2、Callable不是Runnable接口的子接口,所以Callable对象不能直接作为Thread对象的target,此时引入了RunnableFuture接口,RunnableFuture接口是Runnable接口和Future接口的子接口,可以作为Thread对象的target RunnableFuture接口的实现类:FutureTask同样也可以作为Thread对象的target。
    3、调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。

  • FutureTask成员方法

取消该Future里面关联的Callable任务

boolean cancel(boolean mayInterruptIfRunning);

返回Callable里call()方法的返回值,调用这个方法会导致程序阻塞,必须等到子线程结束后才会得到返回值

V get();

传入参数为等待时间,超时抛出超时异常抛出TimeoutException;传入参数为空时,则不设超时,一直等待

V get(long timeout,TimeUnit unit);

Callable任务完成,返回True,否则返回false

boolean isDone();

在Callable任务正常完成前被取消,返回True

boolean isCancelled();

四、使用线程池Executor框架

Executor接口

java.util.concurrent.包下.

  • 优势
    1、执行的业务逻辑Task与线程执行逻辑解耦。
    2、通过框架控制线程的启动、执行、关闭、简化了编码流程,比起Thread.start();更简洁易管理。
    3、线程池重复利用,新任务到来会优先使用空闲线程,找不到空闲线程则创建新线程。
    4、避免this(当前对象)逃逸问题
 void execute(Runnable command);
  • 接收Runnable实例,执行实现Runnable接口的业务。
public interface ExecutorService extends Executor
  • ExecutorService继承自Executor

Executors类

Executors类一系列工厂方法用于创建线程池,返回的线程池实现了ExecutorService接口。

  • 可缓存线程池
    (1)调用execute会优先重用先前构造的可用线程,如果没有可用线程,会创建新线程添加到池中。
    (2)60秒未使用的线程将被终止并从中删除,因此,一个空闲时间足够长的池将不消耗任何资源
    (3)可缓存线程池执行许多短期异步任务的程序,因为其重用线程会提高性能。
    public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                      60L, TimeUnit.SECONDS,
                                      new SynchronousQueue<Runnable>());
    }
  • 固定数目线程池
    (1)该线程池也会优先重用,在队列中运行的固定数量的线程,在需要时使用提供的ThreadFactory创建新线程。
    (2)在任何时候,至多{@code nThreads}个线程处于活动状态处理任务。
    (3)当所有线程处于活动状态时,提交新任务挥会进入阻塞状态,将在队列中等待,直到有线程可用。
    (4)如果任何线程在执行过程中由于失败而终止,那么如果需要执行后续任务,将有一个新线程代替它。
    (5)线程池中的线程将会一直存在,直到它显式{@link ExecutorService#shutdown}。
    public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
        return new ThreadPoolExecutor(nThreads, nThreads,
                                      0L, TimeUnit.MILLISECONDS,
                                      new LinkedBlockingQueue<Runnable>(),
                                      threadFactory);
    }
  • 单线程执行器
    创建单线程执行器,该线程在执行过程中因失败而终止,将会有新线程执行后续操作
   public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }
  • 支持定时任务的线程
    创建一个线程池,该线程池可以设置在给定时间后运行,或定期执行。
   public static ExecutorService newSingleThreadExecutor() {
        return new FinalizableDelegatedExecutorService
            (new ThreadPoolExecutor(1, 1,
                                    0L, TimeUnit.MILLISECONDS,
                                    new LinkedBlockingQueue<Runnable>()));
    }

ExecutorService提供的方法

void shutdown();
  • 关闭自己,调用后停止接收任何新的任务,等待已经提交的任务都完成后关闭ExecutorService。
  • 已经提交的任务包括:
    1)正在执行的
    2)未开始执行的
<T> Future<T> submit(Callable<T> task);
  • 返回参数可跟踪一个或多个异步任务的执行情况
  • 生命状态
    1)运行:submit()创建后便进入运行状态
    2)关闭:shutdown()后不再接受任何新的任务,但已经提交的任务还在进行
    3)终止:所有已经提交的任务全部完成后即为终止状态

Executor执行Callable任务

/**
 * @creater keke
 * @time 2021/1/21 14:08
 * @description
 */
@Slf4j
public class MyExecutor1 {
    public static void main(String[] args) {
        int size = 5;
        List<Future<?>> futures = new ArrayList<Future<?>>();
        ExecutorService executor = Executors.newCachedThreadPool();
        for (int i = 0; i < size; i++) {
            //使用ExecutorService执行Callable类型的任务,并将结果保存在future变量中
            Future<?> future = executor.submit(new MyCallable());
            futures.add(future);
        }
        for (Future<?> fs : futures) {
            try {
                while(!fs.isDone());//Future返回如果没有完成,则一直循环等待,直到Future返回完成
                System.out.println(fs.get());    //打印各个线程(任务)执行的结果

            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            } finally {
                //启动一次顺序关闭,不接受新任务,已提交的顺序执行完毕
                executor.shutdown();
            }
        }
    }
}

Executor执行Runnable任务

/**
 * @creater keke
 * @time 2021/1/21 14:08
 * @description
 */
@Slf4j
public class MyExecutor2 {
    public static void main(String[] args) {
        int size = 10;
        List<Future<?>> futures = new ArrayList<Future<?>>();
        ExecutorService executor = Executors.newFixedThreadPool(size);
        for (int i = 0; i < size; i++) {
             executor.execute(new MyRunnable());
        }
    }
}

五、ThreadPoolExecutor自定义线程池

/**
 * @creater keke
 * @time 2021/1/20 12:44
 * @description
 */
public class MyMain {
    private static final int CORE_POOL_SIZE = 8;//线程池中所保存的线程数,包括空闲线程
    private static final int MAX_POOL_SIZE = 16;//允许的最大线程数为16
    private static final int QUEUE_CAPACITY = 1000; //任务执行前保存任务的队列,仅保存由execute方法提交的Runnable任务。
    private static final Long KEEP_ALIVE_TIME = 1L;//当线程数大于核心数时,该参数为所有的任务终止前,多余的空闲线程等待新任务的最长时间。

    public static void main(String[] args) {
         ThreadPoolExecutor executor = new ThreadPoolExecutor(
                CORE_POOL_SIZE,
                MAX_POOL_SIZE,
                KEEP_ALIVE_TIME,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(QUEUE_CAPACITY),
                new ThreadPoolExecutor.CallerRunsPolicy());
        Runnable runnable1 = new MyRunnable2();
        Runnable runnable2 = new MyRunnable2();
        Runnable runnable3 = new MyRunnable2();
        Runnable runnable4 = new MyRunnable2();
        Runnable runnable5 = new MyRunnable2();
        Runnable runnable6 = new MyRunnable2();
        Runnable runnable7 = new MyRunnable2();

        executor.execute(runnable1);
        executor.execute(runnable2);
        executor.execute(runnable3);
        executor.execute(runnable4);
        executor.execute(runnable5);
        executor.execute(runnable6);
        executor.execute(runnable7);
        //关闭线程池
        executor.shutdown();
    }
}

总结

创建线程的三种方式的对比

1.实现Runnable/Callable接口相比继承Thread类的优势

(1)实现多个线程执行同一个业务资源

(2)业务代码与线程代码分开,更好体现面向对象的模型

(3)Thread继承方式不建议使用, 因为Java是单继承的,继承了Thread就没办法继承其它类了,不够灵活。

2.Callable和Runnable的对比

(1) call()方法可以抛出异常,run()方法不可以

(2) 运行Callable任务可以拿到一个Future对象,可通过查询异步结果了解任务执行情况,可返回当前执行结果,也可取消任务的执行。

3.start()和run()的区别

(1) start()方法用来开启线程,但是线程开启后并没有立即执行,是按CPU分配的时间片来回切换的(当前线程(main)和创建的新线程),他需要获取cpu的执行权才可以执行。

(2) run()方法是由jvm创建完本地操作系统级线程后回调的方法,不可以手动调用。

4.实现Runnable和实现Callable接口的方式基本相同,因此可以把这两种方式归为一种这种方式,与线程池相比,使用线程池可以实现复用,项目开发中主要使用线程池。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值