2.多线程的创建方式

多线程的创建方式共有 4 种

  1. 继承Thread类的方式
  2. 实现Runnable接口的方式
  3. 实现Callable接口的方式
  4. 使用线程池创建

1. 使用继承Thread类的方式创建多线程

1.1 步骤

  1. 创建一个继承与Thread类的子类
  2. 重写Thread类的run() 方法 ,将想要用子线程执行的操作写在 run() 方法中
  3. 在主线程中创建继承Thread类的子类的对象
  4. 调用对象的 start() 方法启动子线程 (注意 这里使用的是 start() 方法 而不是 run() 方法)

1.2 注意点

  1. 不能直接在main方法中调用对象中的 run() 方法,因为如果调用对象的 run() 方法的话,实际是通过主线程执行的
  2. 不能让已经 start() 的子线程再次 start() ,这样会出现 IllegalThreadStateException() , 如果想再开一个子线程的话,需要创兴创建一个继承了 Thread类的子类的对象(下面例子中的 ThreadTest)

1.3 代码实例如下:

public class Main {

    public static void main(String[] args) {
        ThreadTest test = new ThreadTest();

        //调用对象的 start() 方而不是run() 方法
        test.start();
        System.out.println("hello");
    }

}

//ThreadTest 继承 Thread类
class ThreadTest extends Thread{
    
	@Override
    public void run() {
        //想要实现的方法在run方法中执行
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                System.out.println(i);
            }
        }
    }
}

2. 使用 实现Runnable的方式创建多线程

2.1 步骤:

  1. 创建一个实现类Runnable接口的类
  2. 实现类去实现Runnable中的抽象方法:run( )
  3. 创建实现类的对象
  4. 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
  5. 通过Thread类的对象调用start( )

2.2 代码实例如下

public class Main {

    public static void main(String[] args) {

        //2、创建一个实现类的对象
        ThreadTest threadTest = new ThreadTest();

        //3、将实现类的对象作为参数传递到Thread的构造器中
        Thread t = new Thread(threadTest);

        //4、调用thread对象的start方法
        t.start();

        //如果想要再执行一个线程的话:
        Thread t2 = new Thread(threadTest);
        t2.start();
    }

}


//1、创建一个实现了Runnable接口
class ThreadTest implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(i % 2 == 0){
                //此时得到当前线程的名称应该使用Thread.currentThread()的方式
                //因为是实现的Runnable接口,不是继承的Thread类
                System.out.println(Thread.currentThread().getName() + ":" + i);
            }
        }
    }
}

2.3 继承Thread类和实现Runnable接口的区别

都是在run( ) 方法中操作多线程,2种方式创建子线程的方式不同

3. 开发中为什么常使用使用 Runnable的方式创建多线程,而不是使用 继承Thread类的方式?

使用实现Runnable的方式没有类的单继承的局限性

4. 使用实现Callable的方式创建多线程

4.1 使用callable的方式创建多线程思路

1.提出假设
  1. 使用 继承Thread类,和实现Runnable接口的方式创建多线程,都和Thread类有关系,首先找一下Thread中,有没有一个可以传入Callable接口的方式创建多线程的构造方法在这里插入图片描述
  2. 从java8 的官方api文档中,并没有找到传入Callable接口的方式创建多线程的实例 ( ThreadGroup也是一个类 ) 那么就只能类比一下实现Runnable接口的方式,因为Callable和Runnable都是接口
  3. 试想一下,有没有一个类 即和Runnable接口有关系,又和Callable接口有关系那样的话就可以直接传入这个类就可以直接使用这个 Thread(Runnable target, String name) 的构造方法创建多线程了
    1. 那么就有人问了 为什么试想的时候是试想类 而不是试想接口呢:
      1. Runnable接口和Callable接口没有任何关系,一个接口不可以同时继承2个接口 extend a, b( 当然 你要是说 c extend a, d extend c 然后找d 接口 虽然也说得通 但是 emmmmmmmm)
      2. 但是一个类可以同时实现2个接口
2.进入Runnable方法查找
在这里插入图片描述 Runnable接口的子接口有2个, RunnableScheduledFuture 是带时间调度的( Scheduled ),现在不需要时间调度,

那么就点到 RunnableFuture 进去看看 ( 当然,现在也可以点到类中,一个一个的查看和Callable有没有关系,如果实现的类比较多的话 就会比较难找,那我们不妨试试在子接口中查找,看看子接口的实现类 )

3.Runnable接口的子接口 RunnableFuture 中
在这里插入图片描述 首先可以看到 刚才的 RunnableScheduledFuture 接口也是RunnableFuture 的子接口,那么 如果 RunnableFuture接口的实现类中没有关联的类的话,就一定在 RunnableScheduledFuture 接口的实现类中了,那是后话,首先先找一下 RunnableFuture 接口中的实现类和 callable有没有关系,`在这里一看就知道和 SwingWorker没有关系,为什么呢,因为Swing是java做图形化界面的类`, 那我们从FutureTask中查找即可
4.RunnableFuture的实现类 FutureTask中(完成)
在这里插入图片描述 哎呦卧槽,找到了 使用FutureTask对象的时候 ,将Callable接口传入,然后还有一个返回值,好 那么我们就可以使用 new Thread(Runnable runnable, String target的方式创建多线程了 )

4.2 架构图FutureTask类和Runnable接口,Callable接口的关系如下

在这里插入图片描述

4.3 使用实现Callable的方式创建多线程实例

/**
 *
 * 使用Callable解决了Runnable的什么问题
 * 最重要的一点,就是使用Runnable有返回值,可以根据返回值判断线程是否执行出错
 *
 * Callable和Runnable的区别
 * 1.实现的方法不同
 *      Callable使用的是call方法
 *      Runnable使用的是run方法
 * 2.是否抛出异常
 *      Callable可以抛出异常
 *      Runnable不能抛出异常
 * 3.是否有返回值
 *      Callable有返回值
 *      Runnable没有返回值
 *
 * 使用new Thread(Runnable run , String name) 入手, 现在要找到Runnable和Callable的关系
 *      Runnable 是一个接口, 那么说明, Runnable的子接口也可以使用, 找到Runnable子接口的实现类
 *      发现Runnable子接口的实现类可以传入一个Callable接口
 *      根据多态的特性,可以使用new Thread() 的方式创建多线程
 *
 * 注意:get() 方法一般要放在最后一行
 *
 */
public class ImplementCallableTest {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CallableSonClass callableSonClass = new CallableSonClass();
        //得到Runnable和Callable有关联的类
        FutureTask<Integer> futureTask = new FutureTask<>(callableSonClass);

        //创建线程并启动, 使用的是new Thread(Runnable run, String name) 方法
        new Thread(futureTask, "name a").start();

        //如果使用<相同>的 futureTask启动线程, 线程不会再次执行call方法, 而是直接将上次执行完成的call方法的结果拿过来用
//        new Thread(futureTask, "name b").start();

        //get() 方法会导致线程等待, 直到当前线程执行完成后, 在会执行其他线程(相当于是在主线程中,把路给拦住了) 举个例子:高考先做难题
        System.out.println(Thread.currentThread().getName() + "线程执行完成");
        //get() 方法一般要放在最后一行
        futureTask.get();
      
//        System.out.println(Thread.currentThread().getName() + "线程执行完成");
    }

}

class CallableSonClass implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        System.out.println("实现callAble方式创建多线程");
        return 1;
    }
}

4.4 FutureTask的缺点

  1. 使用FutureTask的get方法不管是否计算完成都会导致线程阻塞 等待租借出来再运行 ,get方法要在最后调用

4.5 FutureTask改进

方法1. get(long timeout, TimeUnit unit) 方法

使用FutureTask的 public V get(long timeout, TimeUnit unit) 方法,等待固定时长,如果在这个时长内程序还是没有运行完成,就会出现 juc.TimeOutException 异常

在这里插入图片描述

    public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
        FutureTask<Integer> futureTask = new FutureTask<>(() -> {
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return 1024;
        });
        new Thread(futureTask, "aa").start();
        //不见不散
//        futureTask.get();
	
      	System.out.println("main线程继续执行");
      
      	//改进1, 过时不候
        futureTask.get(2, TimeUnit.SECONDS);
		    
    }

可能出现:等待超时异常

在这里插入图片描述
方法2. 使用CAS思想

缺点:轮询的方式会耗费无谓的CPU资源,而且也不见得能及时的得到计算结果

降低CPU资源解决方案:

  1. 在程序没有执行完成时 让线程等待一会 然后在执行
  2. 指定时间内没有执行完成,直接break
public static void main(String[] args) throws ExecutionException, InterruptedException, TimeoutException {
    FutureTask<Integer> futureTask = new FutureTask<>(() -> {
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 1024;
    });
    new Thread(futureTask, "aa").start();

    System.out.println("main线程继续执行");

    //改进2, CAS思想
    while (true) {
        if (futureTask.isDone()) {
            //futureTask.isDone(),如果futureTask执行完成了, 就返回true
            System.out.println(futureTask.get());
            break;
        } else {
            //1.可以降低轮询的速度, 设置一个等待时间
            //2.使用 System.currentTimeMillis() 判断执行了固定时长 例如5s,如果超过5s 则break
            System.out.println("程序还没有执行完成,继续等待");
        }
    }
}
方法3. 使用CompletableFuture - 工作用

CompletableFuture实现了Future接口和 CompletionStage接口 架构图如下

在这里插入图片描述

CompletionStage是什么

CompletionStage:代表异步计算过程中的某一个阶段,一个阶段完成以后可能会触发另外一个阶段

一句话总结:餐馆点菜

详情:我现在点的一个菜在餐馆中没有材料,老板同事要求3个人把所有材料全部都买回来了,然后厨师下锅把这些材料依次添加进去,最后出锅的是一道已经做好的菜品 而不是材料

一个阶段的计算或执行可以使Function,Consumer或者Runnable。

比如stage.thenApply( x -> square(x) ). thenAccept( x -> System.out.print(x) ). thenRun( () -> System.out.print() )

一个阶段的执行可能是被单个阶段的完成触发,也可能是有多个阶段一起触发

CompletableFuture是什么

CompletableFuture 提供了 Future的扩展功能,可以简化异步编程的复杂性,提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合 CompletableFuture 的方法

CompletableFuture可能代表一个明确的Future,也有可能代表一个完成阶段 ( CompletionStage ), 他支持在计算完成以后出发一些函数或执行某些动作

CompletableFuture实现了 Future接口就相当于是具有了FutureTask的特性,然后在这个基础上有添加了一个新的特性:CompletionStage接口

5.CompletableFuture的4个核心静态方法 创建一个异步操作

4个静态方法中,不用执行 future.get() 方法 程序会自动创建一个线程运行 sync内部的内容,如果调用了 future.get() 方法 使用的是 FutureTask的特性,会将线程阻塞

如果没有指定线程池,直接使用默认的ForkJoinPool.commonPool() 作为线程池执行异步代码

如果指定了线程池,就使用自定义的线程池执行异步代码

无返回值 runAsync()

public static CompletableFuture<Void> runAsync(Runnable runnable)

使用默认的线程池异步开启一个线程进行调用

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
  	System.out.println(Thread.currentThread().getName() + "异步操作");
});
System.out.println("main线程执行");

在这里插入图片描述

public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)

使用自己创建的线程池开启一个线程进行调用

ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
        1,
        5,
        10,
        TimeUnit.SECONDS,
        new LinkedBlockingDeque<>(),
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.AbortPolicy()
);

CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
    System.out.println(Thread.currentThread().getName() + "异步操作");
}, threadPoolExecutor);
System.out.println("main线程执行");

threadPoolExecutor.shutdown();

在这里插入图片描述

有返回值 supplyAsync()

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
    System.out.println(Thread.currentThread().getName() + "");
    return 1;
});
System.out.println(supplyAsync.get());
System.out.println("main线程运行");

在这里插入图片描述

public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
        1,
        5,
        10,
        TimeUnit.SECONDS,
        new LinkedBlockingDeque<>(),
        Executors.defaultThreadFactory(),
        new ThreadPoolExecutor.AbortPolicy()
);

CompletableFuture<Integer> supplyAsync = CompletableFuture.supplyAsync(() -> {
    System.out.println(Thread.currentThread().getName() + "");
    return 2;
}, threadPoolExecutor);
System.out.println(supplyAsync.get());
System.out.println("main线程运行");
threadPoolExecutor.shutdown();
在这里插入图片描述

thenApply方法

在这里插入图片描述

使用核心静态方法后,supplyAsync() 的返回值作为入参 使用这个入参执行一系列操作返回一个结果

whenComplete 、 exceptionally方法

在这里插入图片描述

当上一步操作完成了,判断上一步操作过程中是否出错,将上一步的操作结果、可能会出现的异常作为参数传入,

如果上一步正常执行,则第一个参数就是上一步执行的结果,异常信息就会显示为null

如果上一步异常执行,则第一个参数 为null, 异常信息 是这个异常信息, get() 的结果为 exceptionally() 方法中 return 的信息

举个例子:

        //新开一个子线程干活, 有返回值
        Integer integer = CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + ":completableFuture2");
//           int i = 10 / 0;
            return 1024;
        }).whenComplete((t, u) -> {
            //如果正常执行,就会吧t打印出来
            System.out.println(" --- t= " + t);
            System.out.println(" --- u= " + u);
        }).exceptionally(f -> {
            //只有当程序出错的时候,才会进入这个方法,进入这个方法后, 也会让这个方法有一个异常信息
            System.out.println("--exception:" + f.getMessage());
            return 444;
        }).get();
        System.out.println(integer);

正常执行结果:

在这里插入图片描述

异常执行结果:

在这里插入图片描述

系统级别创建多线程原理

首先在Thread.java类中 调用的是本地方法,实际上调用的是 Thread.c ( c++ 语言),最终是由操作系统级别提供的

native关键字修饰的方法表示JVM通过jvm调用系统底层操作系统的函数方法

在这里插入图片描述

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值