返回介绍

数学基础

统计学习

深度学习

工具

Scala

一、基本概念

发布于 2023-07-17 23:38:22 字数 5549 浏览 0 评论 0 收藏 0

  1. Java 提供了围绕着共享内存和锁构建的并发支持。虽然这种支持是完备的,但是这种并发方案在实际过程中很难正确使用。

    Scala 标准库提供了另一种能够规避这些难点的选择,将程序员的精力集中在不可变状态的异步变换上,即 Future

    虽然 Java 也提供 Future,但是它和 ScalaFuture 不同:

    • 虽然两个 Future 都代表某个异步计算的结果,但是 JavaFuture 要求通过阻塞的 get 方法来访问这个结果。

      虽然在调用 get 方法之前可以先通过调用 idDone 来判断某个 JavaFuture 是否已经完成,从而避免阻塞,但是你却必须等到 JavaFuture 完成之后才能继续用这个结果来进行下一步的计算。

    • ScalaFuture 不同:无论Future 的计算是否完成,都可以指定对它的变换逻辑。每个变换都会产生新的 Future 来表示对原始的 Future 经过给定的函数变换之后产生的异步结果。执行计算的线程由隐式给出的执行上下文 execution context 来决定。这使得你可以将异步的计算描述成一系列的、对不可变值的变换,完成不用考虑共享内存的锁。

  2. Java 平台上,每个对象都关联了一个逻辑监视器moniter ,可以用于控制对数据的多线程访问。此时,需要由你来决定哪些数据将被多个线程共享,并将访问共享数据或者控制对这些共享数据访问的代码段标记为 synchronized

    Java 在运行时将运用一种锁机制来确保同一时间只有一个线程进入由同一个锁控制的同步代码段,从而让你可以协调共享数据的多线程访问。

    为了兼容,Scala 提供了对 Java 并发原语的支持。我们可以用 Scala 调用 wait, notify, notifyAll 等方法,它们的含义跟 Java 中的方法一样。

    从技术上讲,Scala 并没有 synchronized 关键字,不过它有一个 synchronized 方法,可以像这样来调用:

    
    var counter = 0
    synchronized{
      counter += 1  // 这里每次只有一个线程在执行
    }
  3. 事实上,程序员发现通过使用共享数据和锁模型来构建健壮的多线程的应用程序十分困难。难点在于:在程序中的每一点,你都必须推断出哪些你正在修改或访问的数据有可能被其它线程修改或访问,以及在这一点你持有哪些锁。

    每次方法调用,你都必须推断它将会尝试持有那些锁,并判断它有没有可能死锁。而这种推断过程并不是在编译器决定的,而是在运行过程中动态决定的,因为程序在运行过程中可以任意创建新的锁。这进一步加剧了问题的复杂性。

    另外,对于多线程的代码而言,测试是不可靠的。由于线程是非确定性的,可能当你测试 1000 次都是成功的,但是程序第一次在线上运行时就出问题了。

    对于共享数据和锁,你必须通过推断来验证程序的正确性,别无他途。

    另外,你也无法通过过度的同步来解决问题。同步一切并不比什么都不同步要更好。原因是:尽管使用尽可能多的锁来解决竞争问题,但是也增加了死锁的可能性。正确的程序既不应该存在竞争情况,也不应该存在死锁情况。因此无论偏向哪个方向都是不安全的。

    java.util.concurrent 类库提供了并发编程的更高级别的抽象。使用这个工具包来进行多线程编程要比你自己使用更低级别的同步语法更方便,并且引入问题的可能性要小得多。尽管如此,这个工具包还是基于共享数据和锁的,因此并没有从根本上解决这类模型的问题。

  4. ScalaFuture 提供了一种减少(甚至免去)对共享数据和锁进行推断的方式。

    • 当你调用 Scala 方法时,它 “在你等待的过程中” 执行某项计算并返回结果。
    • 如果该结果是一个 Future,则表示另一个将要被异步执行的计算,并且这个计算通常是由另一个完全不同的线程来执行。

    因此,对 Future 的很多操作都需要一个隐式的执行上下文 execution context 来提供异步执行函数的策略。可以通过使用 Scala 提供的一个全局的执行上下文。对 JVM 而言,这个全局的执行上下文使用的是一个线程池。一旦将隐式的执行上下文纳入到作用域中,在可以创建 Future

    ​x
    import scala.concurrent.ExecutionContext.Implicits.global
    import scala.concurrent.Future
    ​
    val fut = Future{ Thread.sleep(10000); 21 + 21} // 对应的线程先睡眠10秒,然后计算出 42
  5. Future 有两个方法让你轮询:

    • isCompleted:判断 Future 的异步计算是否完成。如果已完成则返回 true;如果未完成则返回 false
    • value:返回 Future 异步计算的结果。如果已完成则返回 Some;如果未完成则返回 None
    
    
    xxxxxxxxxx
    val fut = Future{ Thread.sleep(10000); 21 + 21} //***************** 10 秒钟以内 **************// fut.isCompleted // 返回 false fut.value // 返回 None //***************** 10 秒钟以后 **************// fut.isCompleted // 返回 true fut.value // 返回 Some(Success(42))

    value 的返回值类型其实是 Option[scala.util.Try[T]] 。当 Future 计算已完成时,Some 包含一个 Try 类型的对象。Try 对象要么是包含类型为 T 的值的 Success、要么是包含一个异常(java.lang.Throwable 实例)的 Failure

    Try 的目的是为异步计算提供一种与同步计算中 try 表达式类似的东西:允许你处理那些有可能异常终止而不是返回正常结果的情况。

    Try 的继承关系如下:

    
    
    xxxxxxxxxx
    scala.util Try[+T] << sealed abstract >> ^ ^ / \ / \ scala.util scala.util Success[T] Failure[T] << final case >> << final case >>

    对同步计算而言,你可以通过使用 try / catch 来确保调用某个方法的线程可以捕获并处理由该方法抛出的异常。不过对于异步计算而言,发起该计算的线程通常都转到别的任务了。在这之后,如果异步计算因为某个异常失败了,原始的线程就无法再用 catch 来捕获这个异常了。因此,当处理表示异步计算的 Future 时,你需要用 Try 来处理这种情况:该活动未能交出某个结果,而是异常终止了。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文