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);
    }
}

相关文章
|
9天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
5天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
25 9
|
8天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####
|
5天前
|
监控 安全 Java
Java中的多线程编程:从入门到实践####
本文将深入浅出地探讨Java多线程编程的核心概念、应用场景及实践技巧。不同于传统的摘要形式,本文将以一个简短的代码示例作为开篇,直接展示多线程的魅力,随后再详细解析其背后的原理与实现方式,旨在帮助读者快速理解并掌握Java多线程编程的基本技能。 ```java // 简单的多线程示例:创建两个线程,分别打印不同的消息 public class SimpleMultithreading { public static void main(String[] args) { Thread thread1 = new Thread(() -> System.out.prin
|
8天前
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是实现线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件满足时被唤醒,从而确保数据一致性和同步。相比其他通信方式,如忙等待,这些方法更高效灵活。 示例代码展示了如何在生产者-消费者模型中使用这些方法实现线程间的协调和同步。
22 3
|
7天前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
8天前
|
Java
java小知识—进程和线程
进程 进程是程序的一次执行过程,是系统运行的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。 线程 线程,与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间做切换工作时,负担要比
19 1
|
8天前
|
Java UED
Java中的多线程编程基础与实践
【10月更文挑战第35天】在Java的世界中,多线程是提升应用性能和响应性的利器。本文将深入浅出地介绍如何在Java中创建和管理线程,以及如何利用同步机制确保数据一致性。我们将从简单的“Hello, World!”线程示例出发,逐步探索线程池的高效使用,并讨论常见的多线程问题。无论你是Java新手还是希望深化理解,这篇文章都将为你打开多线程的大门。
|
9天前
|
安全 Java 编译器
Java多线程编程的陷阱与最佳实践####
【10月更文挑战第29天】 本文深入探讨了Java多线程编程中的常见陷阱,如竞态条件、死锁、内存一致性错误等,并通过实例分析揭示了这些陷阱的成因。同时,文章也分享了一系列最佳实践,包括使用volatile关键字、原子类、线程安全集合以及并发框架(如java.util.concurrent包下的工具类),帮助开发者有效避免多线程编程中的问题,提升应用的稳定性和性能。 ####
36 1
|
12天前
|
存储 设计模式 分布式计算
Java中的多线程编程:并发与并行的深度解析####
在当今软件开发领域,多线程编程已成为提升应用性能、响应速度及资源利用率的关键手段之一。本文将深入探讨Java平台上的多线程机制,从基础概念到高级应用,全面解析并发与并行编程的核心理念、实现方式及其在实际项目中的应用策略。不同于常规摘要的简洁概述,本文旨在通过详尽的技术剖析,为读者构建一个系统化的多线程知识框架,辅以生动实例,让抽象概念具体化,复杂问题简单化。 ####