Java 多线程学习(二)

简介: Java 多线程学习

2.3 第三种:实现 Callable 接口

Callable接口详解_逍遥绝情的博客-CSDN博客_callable接口(不懂看这个博客)

Callable两种执行方式:第一种借助线程池来运行

  1. 实现 Callable 接口,需要返回值类型
  2. 重写 call 方法,需要抛出异常
  3. 创建目标对象
  4. 创建执行服务:ExecutorService = Executor.newFixedThreadPool(1);
  5. 提交执行:Future result1 = ser.submit(1);
  6. 获取结果:boolean r1 = result1.get()
  7. 关闭服务:ser.shutdownNow():

Future接口
Future是一个接口,代表了一个异步计算的结果。接口中的方法用来检查计算是否完成、
等待完成和得到计算的结果。
当计算完成后,只能通过get()方法得到结果,get方法会阻塞直到结果准备好了。
如果想取消,那么调用cancel()方法。其他方法用于确定任务是正常完成还是取消了。
一旦计算完成了,那么这个计算就不能被取消。

package com.example.democrud.democurd.test01;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
//Callable<Boolean>  布尔类型和call 同步
//callable的好处:
//1.可以定义返回值
//2.可以抛出异常
public class testThred02 implements Callable<Boolean> {
    public String url;//图片路径
    public String name;//图片的名字
    public testThred02(String url, String name) {
        this.name = name;
        this.url = url;
    }
    @Override //代表重写
    public Boolean call() {
        downloadpng down = new downloadpng();
        down.download(url, name);
        System.out.println("下载的文件名为" + name);
        return true;
    }
    //主线程 main方法
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        testThred02 thred01 = new testThred02("https://img1.baidu.com/it/u=413643897,2296924942&fm=253&app=138&size=w931&n=0&f=JPEG&fmt=auto?sec=1680541200&t=26be472b80013591100109eb63c7c5ec", "test01.jpg");
        testThred02 thred02 = new testThred02("https://img1.baidu.com/it/u=307074048,654359288&fm=253&app=120&size=w931&n=0&f=JPEG&fmt=auto?sec=1680541200&t=554384a7bbd003adba3de4aaa73365d4", "test02.jpg");
        testThred02 thred03 = new testThred02("https://img1.baidu.com/it/u=307074048,654359288&fm=253&app=120&size=w931&n=0&f=JPEG&fmt=auto?sec=1680541200&t=554384a7bbd003adba3de4aaa73365d4", "test03.jpg");
        // 创建执行服务  线程池中有3个线程
        ExecutorService ser = Executors.newFixedThreadPool(3);
        // 提交执行 执行每个线程
        Future<Boolean> result1 = ser.submit(thred01);
        Future<Boolean> result2 = ser.submit(thred02);
        Future<Boolean> result3 = ser.submit(thred03);
        // 获取结果:
        boolean r1 = result1.get();
        boolean r2 = result2.get();
        boolean r3 = result3.get();
        //关闭服务
        ser.shutdownNow();
    }
    //先写一个下载器
    class downloadpng {
        //引用地址和名字进行下载
        public void download(String url, String name) {
            try {
                FileUtils.copyURLToFile(new URL(url), new File(name));
            } catch (IOException e) {
                e.printStackTrace();
                System.out.println("IO异常,downloader 方法出现问题");
            }
        }
    }
}

Callable两种执行方式:第二种借助FutureTask执行

示例代码中,CallableTest 类实现了 Callable 接口的 call() 方法。在 main() 函数内首先创建了一个 FutureTask 对象(构造函数为 CallableTest 实例),然后使用创建的 FutureTask 对象作为任务创建了一个线程并且启动它,最后通过 futureTask.get() 等待任务执行完毕返回结果。

Future接口
Future是一个接口,代表了一个异步计算的结果。接口中的方法用来检查计算是否完成、
等待完成和得到计算的结果。
当计算完成后,只能通过get()方法得到结果,get方法会阻塞直到结果准备好了。
如果想取消,那么调用cancel()方法。其他方法用于确定任务是正常完成还是取消了。
一旦计算完成了,那么这个计算就不能被取消。
FutureTask类
FutureTask类实现了RunnableFuture接口,而RunnnableFuture接口继承了Runnable和Future接口,所以说FutureTask是一个提供异步计算的结果的任务。
FutureTask有2个构造方法:入参分别是Callable或者Runnbale对象。
FutureTask类同时实现了两个接口,Future和Runnable接口,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。

public class FutureTask<V> implements RunnableFuture<V>{}
public interface RunnableFuture<V> extends Runnable, Future<V>

代码示例:

package com.sjmp.practice;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableTest implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "Hello CallableThread";
    }
    public static void main(String[] args) {
        CallableTest callableTest = new CallableTest();
        FutureTask<String> futureTask = new FutureTask<>(callableTest);
        new Thread(futureTask).start();
        try {
            String s = futureTask.get();
            System.out.println(s);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
Hello CallableThread
Process finished with exit code 0

Runnable和Callable的区别(重要)

相同点
1、两者都是接口;(废话)
2、两者都可用来编写多线程程序;
3、两者都需要调用Thread.start()启动线程;
不同点
1、两者最大的不同点是:实现Callable接口的任务线程能返回执行结果;
而实现Runnable接口的任务线程不能返回结果;
2、Callable接口的call()方法允许抛出异常;
而Runnable接口的run()方法的异常只能在内部消化,不能继续上抛;
注意点
Callable接口支持返回执行结果,此时需要调用FutureTask.get()方法实现,此方法会阻塞主线程直到获取‘将来’结果;当不调用此方法时,主线程不会阻塞!

扩展(重要):

Callable接口详解

  • Callable: 返回结果并且可能抛出异常的任务。
  • 优点:
  • 可以获得任务执行返回值;
  • 通过与Future的结合,可以实现利用Future来跟踪异步计算的结果。

Runnable和Callable的区别:

  • 1、Callable规定的方法是call(),Runnable规定的方法是run().
  • 2、Callable的任务执行后可返回值,而Runnable的任务是不能返回值得
  • 3、call方法可以抛出异常,run方法不可以
  • 4、运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
  • 5、代码示例:
//Callable 接口
  public interface Callable<V> {
     V call() throws Exception;
  }
  // Runnable 接口
  public interface Runnable {
      public abstract void run();
  }

Future接口

  • Future是一个接口,代表了一个异步计算的结果。接口中的方法用来检查计算是否完成、等待完成和得到计算的结果。
  • 当计算完成后,只能通过get()方法得到结果,get方法会阻塞直到结果准备好了。
  • 如果想取消,那么调用cancel()方法。其他方法用于确定任务是正常完成还是取消了。
  • 一旦计算完成了,那么这个计算就不能被取消。

FutureTask类

  • FutureTask类实现了RunnableFuture接口,而RunnnableFuture接口继承了Runnable和Future接口,所以说FutureTask是一个提供异步计算的结果的任务。
  • FutureTask可以用来包装Callable或者Runnbale对象。因为FutureTask实现了Runnable接口,所以FutureTask也可以被提交给Executor(如上面例子那样)。

Callable两种执行方式

  • 1、借助FutureTask执行
  • FutureTask类同时实现了两个接口,Future和Runnable接口,所以它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。
  • 具体流程:
//定义实现Callable接口的的实现类重写call方法。
  public class MyCallableTask implements Callable<Integer>{
      @Override
          public Integer call() throws Exception {
             //TODO 线程执行方法
          }
  }
  ---------------------------------------------------------
  //创建Callable对象
  Callable<Integer> mycallabletask = new MyCallableTask();
  //开始线程
  FutureTask<Integer> futuretask= new FutureTask<Integer>(mycallabletask);
  new Thread(futuretask).start();
  --------------------------------------------------------
  通过futuretask可以得到MyCallableTask的call()的运行结果:
  futuretask.get();
  • 2、借助线程池来运行
  • 线程池中执行Callable任务原型:
public interface ExecutorService extends Executor {
      //提交一个Callable任务,返回值为一个Future类型
      <T> Future<T> submit(Callable<T> task);
          //other methods...
  }
  • 借助线程池来运行Callable任务的一般流程为:
ExecutorService exec = Executors.newCachedThreadPool();
   Future<Integer> future = exec.submit(new MyCallableTask());
  • 通过future可以得到MyCallableTask的call()的运行结果: future.get();

举例说明

  • 例1:
public class CallableTest {
      public static void main(String[] args) throws ExecutionException, InterruptedException,TimeoutException{
          //创建一个线程池
          ExecutorService executor = Executors.newCachedThreadPool();
          Future<String> future = executor.submit(()-> {
                  TimeUnit.SECONDS.sleep(5);
                  return "CallableTest";
          });
          System.out.println(future.get());
          executor.shutdown();
      }
  }
  • 例2:Callable任务借助FutureTask运行:
public class CallableAndFutureTask {
      Random random = new Random();
      public static void main(String[] args) {
          Callable<Integer> callable = new Callable<Integer>() {
              public Integer call() throws Exception {
                  return random.nextInt(10000);
              }
          };
          FutureTask<Integer> future = new FutureTask<Integer>(callable);
          Thread thread = new Thread(future);
          thread.start();
          try {
              Thread.sleep(2000);
              System.out.println(future.get());
          } catch (Exception e) {
              e.printStackTrace();
          }
      }
  }
  • 例3:Callable任务和线程池一起使用,然后返回值是Future:
public class CallableAndFuture {
       Random random = new Random();
       public static void main(String[] args) {
           ExecutorService threadPool = Executors.newSingleThreadExecutor();
           Future<Integer> future = threadPool.submit(new Callable<Integer>() {
               public Integer call() throws Exception {
                   return random.nextInt(10000);
               }
           });
           try {
               Thread.sleep(3000);
               System.out.println(future.get());
           } catch (Exception e) {
               e.printStackTrace();
           }
       }
   }
  • 例4:当执行多个Callable任务,有多个返回值时,我们可以创建一个Future的集合:
class MyCallableTask implements Callable<String> {
      private int id;
      public OneTask(int id){
          this.id = id;
      }
      @Override
      public String call() throws Exception {
          for(int i = 0;i<5;i++){
              System.out.println("Thread"+ id);
              Thread.sleep(1000);
          }
          return "Result of callable: "+id;
      }
  }
  public class Test {
      public static void main(String[] args) {
          ExecutorService exec = Executors.newCachedThreadPool();
          ArrayList<Future<String>> results = new ArrayList<Future<String>>();
          for (int i = 0; i < 5; i++) {
              results.add(exec.submit(new MyCallableTask(i)));
          }
          for (Future<String> fs : results) {
              if (fs.isDone()) {
                  try {
                      System.out.println(fs.get());
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
              } else {
                  System.out.println("MyCallableTask任务未完成!");
              }
          }
          exec.shutdown();
      }
  }

StopWatch的使用

  • Spring提供的计时器StopWatch对于秒、毫秒为单位方便计时的程序,尤其是单线程、顺序执行程序的时间特性的统计输出支持比较好。也就是说假如我们手里面有几个在顺序上前后执行的几个任务,而且我们比较关心几个任务分别执行的时间占用状况,希望能够形成一个不太复杂的日志输出,StopWatch提供了这样的功能。而且Spring的StopWatch基本上也就是仅仅为了这样的功能而实现。
public String call() throws Exception {
    StopWatch stopWatch = new StopWatch();
    stopWatch.start("测试StopWatch");
    //TODO 业务逻辑
    stopWatch.stop();
    return "test";
  }

2.4 Lamda 表达式

Lamda 表达式属于函数式编程的概念

  • 理解 Functional Interface(函数式接口)是学习 Java8 Lambda 表达式的关键所在。
  • 函数式接口:说起Lambda,就必须了解函数式接口,因为要使用Lambda,必须在函数式接口上使用。函数式接口:就是一个有且仅有一个抽象方法,但是可以有多个默认方法的接口,这样的接口可以隐式转换为Lambda表达式。一般在函数式接口上都有个注解@FunctionalInterface,该注解的作用类似@Override一样告诉编译器这是一个函数式接口,用于编译期间检测该接口是否仅有一个抽象方法,如果拥有多个则编译不通过。

Lamda 表达式的演进:

package com.example.democrud.democurd.test01;
public class testLambda {
    //3.静态内部类
    static class like2 implements iLike{
        @Override
        public void lamdba() {
            System.out.println("我在学习lamdba-2");
        }
    }
    public static void main(String[] args) {
        iLike like = new like();
        like.lamdba();
        like = new like2();
        like.lamdba();
        //4.局部内部类
        class like3 implements iLike{
            @Override
            public void lamdba() {
                System.out.println("我在学习lamdba-3");
            }
        }
        like=new like3();
        like.lamdba();
        //5.匿名内部类 没有类的名称必须借助接口或者父类
        like=new iLike() {
            @Override
            public void lamdba() {
                System.out.println("我在学习lamdba-4");
            }
        };
        like.lamdba();
         //6.lambda简化
        like=()->{
            System.out.println("我在学习lamdba-5");
        };
        like.lamdba();
    }
}
//1.定义一个函数式接口
interface iLike{
    void lamdba();
}
//2.实现类
class like implements iLike{
    @Override
    public void lamdba() {
        System.out.println("我在学习lamdba-1");
    }
}

DEMO:

package com.example.democrud.democurd.test01;
public class testLamdba01 {
    //静态内部类
    static class Love1 implements iLove {
        @Override
        public void love(int a) {
            System.out.println("打印我输入的数字是" + a);
        }
    }
    public static void main(String[] args) {
        iLove love = new Love();
        love.love(20);
        love = new Love1();
        love.love(12);
//匿名类
        love = new iLove() {
            @Override
            public void love(int a) {
                System.out.println("打印我输入的数字是" + a);
            }
        };
        love.love(33);
        //lambba简化
        love = (int a) -> {
            System.out.println("打印我输入的数字是" + a);
        };
        love.love(56);
        //简化参数类型
        love = (a) -> {
            System.out.println("打印我输入的数字是" + a);
        };
        love.love(57);
        //简化中括号
        love = a -> {
            System.out.println("打印我输入的数字是" + a);
        };
        love.love(58);
        //简化花括号
        love = a -> System.out.println("打印我输入的数字是" + a);
        love.love(59);
        //总结:lamdba表达式只能有代码一行的情况下 才能简化一行,多行用大括号包围
          //用户函数是接口
        //函数是接口:任何接口如果只包含唯一一个 抽象方法那么他就是一个函数式接口
    }
}
interface iLove {
    void love(int a);
}
//实现类
class Love implements iLove {
    @Override
    public void love(int a) {
        System.out.println("打印我输入的数字是" + a);
    }
}

相关文章
|
3天前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
36 14
|
6天前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
34 13
|
6天前
|
安全 Java 开发者
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。
|
26天前
|
Java 调度 开发者
Java线程池ExecutorService学习和使用
通过学习和使用Java中的 `ExecutorService`,可以显著提升并发编程的效率和代码的可维护性。合理配置线程池参数,结合实际应用场景,可以实现高效、可靠的并发处理。希望本文提供的示例和思路能够帮助开发者深入理解并应用 `ExecutorService`,实现更高效的并发程序。
33 10
|
28天前
|
Java 数据库连接 数据库
【潜意识Java】深度分析黑马项目《苍穹外卖》在Java学习中的重要性
《苍穹外卖》项目对Java学习至关重要。它涵盖了用户管理、商品查询、订单处理等模块,涉及Spring Boot、MyBatis、Redis等技术栈。
76 4
|
28天前
|
前端开发 Java 数据库连接
【潜意识Java】深度解读JavaWeb开发在Java学习中的重要性
深度解读JavaWeb开发在Java学习中的重要性
29 4
|
28天前
|
存储 移动开发 算法
【潜意识Java】Java基础教程:从零开始的学习之旅
本文介绍了 Java 编程语言的基础知识,涵盖从简介、程序结构到面向对象编程的核心概念。首先,Java 是一种高级、跨平台的面向对象语言,支持“一次编写,到处运行”。接着,文章详细讲解了 Java 程序的基本结构,包括包声明、导入语句、类声明和 main 方法。随后,深入探讨了基础语法,如数据类型、变量、控制结构、方法和数组。此外,还介绍了面向对象编程的关键概念,例如类与对象、继承和多态。最后,针对常见的编程错误提供了调试技巧,并总结了学习 Java 的重要性和方法。适合初学者逐步掌握 Java 编程。
52 1
|
1月前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
109 17
|
2月前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
1月前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题

热门文章

最新文章