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

相关文章
|
8天前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
10天前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。
|
10天前
|
消息中间件 缓存 安全
Java多线程是什么
Java多线程简介:本文介绍了Java中常见的线程池类型,包括`newCachedThreadPool`(适用于短期异步任务)、`newFixedThreadPool`(适用于固定数量的长期任务)、`newScheduledThreadPool`(支持定时和周期性任务)以及`newSingleThreadExecutor`(保证任务顺序执行)。同时,文章还讲解了Java中的锁机制,如`synchronized`关键字、CAS操作及其实现方式,并详细描述了可重入锁`ReentrantLock`和读写锁`ReadWriteLock`的工作原理与应用场景。
|
10天前
|
安全 Java 编译器
深入理解Java中synchronized三种使用方式:助您写出线程安全的代码
`synchronized` 是 Java 中的关键字,用于实现线程同步,确保多个线程互斥访问共享资源。它通过内置的监视器锁机制,防止多个线程同时执行被 `synchronized` 修饰的方法或代码块。`synchronized` 可以修饰非静态方法、静态方法和代码块,分别锁定实例对象、类对象或指定的对象。其底层原理基于 JVM 的指令和对象的监视器,JDK 1.6 后引入了偏向锁、轻量级锁等优化措施,提高了性能。
33 3
|
10天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
91 2
|
18天前
|
安全 Java API
java如何请求接口然后终止某个线程
通过本文的介绍,您应该能够理解如何在Java中请求接口并根据返回结果终止某个线程。合理使用标志位或 `interrupt`方法可以确保线程的安全终止,而处理好网络请求中的各种异常情况,可以提高程序的稳定性和可靠性。
46 6
|
1月前
|
存储 监控 小程序
Java中的线程池优化实践####
本文深入探讨了Java中线程池的工作原理,分析了常见的线程池类型及其适用场景,并通过实际案例展示了如何根据应用需求进行线程池的优化配置。文章首先介绍了线程池的基本概念和核心参数,随后详细阐述了几种常见的线程池实现(如FixedThreadPool、CachedThreadPool、ScheduledThreadPool等)的特点及使用场景。接着,通过一个电商系统订单处理的实际案例,分析了线程池参数设置不当导致的性能问题,并提出了相应的优化策略。最终,总结了线程池优化的最佳实践,旨在帮助开发者更好地利用Java线程池提升应用性能和稳定性。 ####
|
27天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
27天前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
50 3
|
28天前
|
监控 Java 开发者
深入理解Java中的线程池实现原理及其性能优化####
本文旨在揭示Java中线程池的核心工作机制,通过剖析其背后的设计思想与实现细节,为读者提供一份详尽的线程池性能优化指南。不同于传统的技术教程,本文将采用一种互动式探索的方式,带领大家从理论到实践,逐步揭开线程池高效管理线程资源的奥秘。无论你是Java并发编程的初学者,还是寻求性能调优技巧的资深开发者,都能在本文中找到有价值的内容。 ####