一文带你深入理解Go语言中的sync.Cond

目录
  • sync.Cond 是什么
    • 适用场景
  • sync.Cond 的基本用法
    • NewCond 创建实例
    • Wait 等待条件满足
    • Signal 通知一个等待的 goroutine
    • Broadcast 通知所有等待的 goroutine
  • sync.Cond 使用实例
  • 为什么要用 sync.Cond
    • close channel 广播实例
  • sync.Cond 基本原理
  • sync.Cond 的设计与实现
    • sync.Cond 模型
    • notifyList 结构体
    • sync.Cond 的方法
    • Wait 方法
    • Signal 方法
    • Broadcast 方法
    • copyChecker 结构体
  • 为什么 sync.Cond 不能被复制
  • 总结

在 go 的标准库中,提供了 sync.Cond 这个并发原语,让我们可以实现多个 goroutine 等待某一条件满足之后再继续执行。 它需要配合 sync.Mutex 一起使用,因为 CondWait 方法需要在 Mutex 的保护下才能正常工作。 对于条件变量,可能大多数人只是知道它的存在,但是用到它的估计寥寥无几,因为很多并发场景的处理都能使用 chan 来实现, 而且 chan 的使用也更加简单。 但是在某些场景下,Cond 可能是最好的选择,本文就来探讨一下 Cond 的使用场景,基本用法,以及它的实现原理。

sync.Cond 是什么

sync.Cond 表示的是条件变量,它是一种同步机制,用来协调多个 goroutine 之间的同步,当共享资源的状态发生变化的时候, 可以通过条件变量来通知所有等待的 goroutine 去重新获取共享资源。

适用场景

在实际使用中,我们可能会有多个 goroutine 在执行的过程中,由于某一条件不满足而阻塞的情况。 这个时候,我们就可以使用条件变量来实现 goroutine 之间的同步。比如,我们有一个 goroutine 用来获取数据, 但是可能会比较耗时,这个时候,我们就可以使用条件变量来实现 goroutine 之间的同步, 当数据准备好之后,就可以通过条件变量来通知所有等待的 goroutine 去重新获取共享资源。

sync.Cond 条件变量用来协调想要访问共享资源的那些 goroutine,当共享资源的状态发生变化的时候, 它可以用来通知所有等待的 goroutine 去重新获取共享资源。

sync.Cond 的基本用法

sync.Cond 的基本用法非常简单,我们只需要通过 sync.NewCond 方法来创建一个 Cond 实例, 然后通过 Wait 方法来等待条件满足,通过 Signal 或者 Broadcast 方法来通知所有等待的 goroutine 去重新获取共享资源。

NewCond 创建实例

sync.NewCond 方法用来创建一个 Cond 实例,它的参数是一个 Locker 接口,我们可以传入一个 Mutex 或者 RWMutex 实例。 这个条件变量的 Locker 接口就是用来保护共享资源的。

Wait 等待条件满足

Wait 方法用来等待条件满足,它会先释放 Cond 的锁(Cond.L),然后阻塞当前 goroutine(实际调用的是 goparkunlock),直到被 Signal 或者 Broadcast 唤醒。

它做了如下几件事情:

  • 释放 Cond 的锁(Cond.L),然后阻塞当前 goroutine。(所以,使用之前需要先锁定)
  • Signal 或者 Broadcast 唤醒之后,会重新获取 Cond 的锁(Cond.L)。
  • 之后,就返回到 goroutine 阻塞的地方继续执行。

Signal 通知一个等待的 goroutine

Signal 方法用来通知一个等待的 goroutine,它会唤醒一个等待的 goroutine,然后继续执行当前 goroutine。 如果没有等待的 goroutine,则不会有任何操作。

Broadcast 通知所有等待的 goroutine

Broadcast 方法用来通知所有等待的 goroutine,它会唤醒所有等待的 goroutine,然后继续执行当前 goroutine。 如果没有等待的 goroutine,则不会有任何操作。

sync.Cond 使用实例

下面我们通过一个实例来看一下 sync.Cond 的使用方法。

package cond

import (
   "fmt"
   "sync"
   "testing"
   "time"
)

var done bool
var data string

func write(c *sync.Cond) {
   fmt.Println("writing.")
   // 让 reader 先获取锁,模拟条件不满足然后 wait 的情况
   time.Sleep(time.Millisecond * 10)
   c.L.Lock()
   // 模拟耗时的写操作
   time.Sleep(time.Millisecond * 50)
   data = "hello world"
   done = true
   fmt.Println("writing done.")
   c.L.Unlock()
   c.Broadcast()
}

func read(c *sync.Cond) {
   fmt.Println("reading")
   c.L.Lock()
   for !done {
      fmt.Println("reader wait.")
      c.Wait()
   }
   fmt.Println("read done.")
   fmt.Println("data:", data)
   defer c.L.Unlock()
}

func TestCond(t *testing.T) {
   var c = sync.NewCond(&sync.Mutex{})

   go read(c)  // 读操作
   go read(c)  // 读操作
   go write(c) // 写操作

   time.Sleep(time.Millisecond * 100) // 等待操作完成
}

输出:

reading
reader wait. // 还没获取完数据,需要等待
writing.
reading
reader wait.
writing done. // 获取完数据了,通知所有等待的 reader
read done. // 读取到数据了
data: hello world // 输出读取到的数据
read done.
data: hello world

这个例子可以粗略地用下图来表示:

说明:

  • read1reader2 表示两个 goroutine,它们都会调用 read 函数。
  • donefalse 的时候,reader1reader2 都会调用 c.Wait() 函数,然后阻塞等待。
  • write 表示一个 goroutine,它会调用 write 函数。
  • write 函数中,获取完数据之后,会将 done 设置为 true,然后调用 c.Broadcast() 函数,通知所有等待的 reader 去重新获取共享资源。
  • reader1reader2 在解除阻塞状态后,都会重新获取共享资源,然后输出读取到的数据。

在这个例子中,done 的功能是标记,用来表示共享资源是否已经获取完毕,如果没有获取完毕,那么 reader 就会阻塞等待。

为什么要用 sync.Cond

在文章开头,我们说了,很多并发编程的问题都可以通过 channel 来解决。 同样的,在上面提到的 sync.Cond 的使用场景,使用 channel 也是可以实现的, 我们只要 close(ch) 来关闭 channel 就可以实现通知多个等待的协程了。

那么为什么还要用 sync.Cond 呢? 主要原因是,sync.Cond 可以重复地进行 Wait()Signal()Broadcast() 操作, 但是,如果想通过关闭 chan 来实现这个功能的话,那就只能通知一次了。 因为 channel 只能关闭一次,关闭一个已经关闭的 channel 会导致程序 panic。

使用 channel 的另外一种方式是,记录 reader 的数量,然后通过往 channel 中发送多次数据来实现通知多个 reader。 但是这样一来代码就会复杂很多,从另一个角度说,出错的概率大了很多。

close channel 广播实例

下面的例子模拟了使用 close(chan) 来实现 sync.Cond 中那种广播功能,但是只能通知一次。

package close_chan

import (
   "fmt"
   "testing"
   "time"
)

var data string

func read(c <-chan struct{}) {
   fmt.Println("reading.")

   // 从 chan 接收数据,如果 chan 中没有数据,会阻塞。
   // 如果能接收到数据,或者 chan 被关闭,会解除阻塞状态。
   <-c

   fmt.Println("data:", data)
}

func write(c chan struct{}) {
   fmt.Println("writing.")
   // 模拟耗时的写操作
   time.Sleep(time.Millisecond * 10)
   data = "hello world"
   fmt.Println("write done.")

   // 关闭 chan 的时候,会通知所有的 reader
   // 所有等待从 chan 接收数据的 goroutine 都会被唤醒
   close(c)
}

func TestCloseChan(t *testing.T) {
   ch := make(chan struct{})

   go read(ch)
   go read(ch)
   go write(ch)

   // 不能关闭已经关闭的 chan
   time.Sleep(time.Millisecond * 20)
   // panic: close of closed channel
   // 下面这行代码会导致 panic
   //go write(ch)

   time.Sleep(time.Millisecond * 100)
}

输出:

writing.
reading. // 会阻塞直到写完
reading. // 会阻塞直到写完
write done. // 写完之后,才能读
data: hello world
data: hello world

上面例子的 write 不能多次调用,否则会导致 panic。

sync.Cond 基本原理

go 的 sync.Cond 中维护了一个链表,这个链表记录了所有阻塞的 goroutine,也就是由于调用了 Wait 而阻塞的 goroutine。 而 SignalBroadcast 方法就是用来唤醒这个链表中的 goroutine 的。 Signal 方法只会唤醒链表中的第一个 goroutine,而 Broadcast 方法会唤醒链表中的所有 goroutine

下图是 Signal 方法的效果,可以看到,Signal 方法只会唤醒链表中的第一个 goroutine

说明:

  • notifyListsync.Cond 中维护的一个链表,这个链表记录了所有阻塞的 goroutine
  • head 是链表的头节点,tail 是链表的尾节点。
  • Signal 方法只会唤醒链表中的第一个 goroutine

Broadcast 方法会唤醒 notifyList 中的所有 goroutine

sync.Cond 的设计与实现

最后,我们来看一下 sync.Cond 的设计与实现。

sync.Cond 模型

sync.Cond 的模型如下所示:

type Cond struct {
   noCopy noCopy

   // L is held while observing or changing the condition
   L Locker // L 在观察或改变条件时被持有

   notify  notifyList
   checker copyChecker
}

属性说明:

  • noCopy 是一个空结构体,用来检查 sync.Cond 是否被复制。(在编译前通过 go vet 命令来检查)
  • L 是一个 Locker 接口,用来保护条件变量。
  • notify 是一个 notifyList 类型,用来记录所有阻塞的 goroutine
  • checker 是一个 copyChecker 类型,用来检查 sync.Cond 是否被复制。(如果在运行时被复制,会导致 panic

notifyList 结构体

notifyListsync.Cond 中维护的一个链表,这个链表记录了所有因为共享资源还没准备好而阻塞的 goroutine。它的定义如下所示:

type notifyList struct {
   wait atomic.Uint32
   notify uint32

   // 阻塞的 waiter 名单。
   lock mutex // 锁
   head *sudog // 阻塞的 goroutine 链表(链表头)
   tail *sudog // 阻塞的 goroutine 链表(链表尾)
}

属性说明:

  • wait 是下一个 waiter 的编号。它在锁外自动递增。
  • notify 是下一个要通知的 waiter 的编号。它可以在锁外读取,但只能在持有锁的情况下写入。
  • lock 是一个 mutex 类型,用来保护 notifyList
  • head 是一个 sudog 类型,用来记录阻塞的 goroutine 链表的头节点。
  • tail 是一个 sudog 类型,用来记录阻塞的 goroutine 链表的尾节点。

notifyList 的方法说明:

notifyList 中包含了几个操作阻塞的 goroutine 链表的方法。

  • notifyListAdd 方法将 waiter 的编号加 1。
  • notifyListWait 方法将当前的 goroutine 加入到 notifyList 中。(也就是将当前协程挂起)
  • notifyListNotifyOne 方法将 notifyList 中的第一个 goroutine 唤醒。
  • notifyListNotifyAll 方法将 notifyList 中的所有 goroutine 唤醒。
  • notifyListCheck 方法检查 notifyList 的大小是否正确。

sync.Cond 的方法

notifyList 就不细说了,本文重点讲解一下 sync.Cond 的实现。

Wait 方法

Wait 方法用在当条件不满足的时候,将当前运行的协程挂起。

func (c *Cond) Wait() {
   // 检查是否被复制
   c.checker.check()
   // 更新 notifyList 中需要等待的 waiter 的数量
   // 返回当前需要插入 notifyList 的编号
   t := runtime_notifyListAdd(&c.notify)
   // 解锁
   c.L.Unlock()
   // 挂起当前 g,直到被唤醒
   runtime_notifyListWait(&c.notify, t)
   // 唤醒之后,重新加锁。
   // 因为阻塞之前解锁了。
   c.L.Lock()
}

对于 Wait 方法,我们需要注意的是,使用之前,我们需要先调用 L.Lock() 方法加锁,然后再调用 Wait 方法,否则会报错。

文档里面的例子:

c.L.Lock()
for !condition() {
    c.Wait()
}
// ...使用条件...
// 这里是我们在条件满足之后,需要执行的代码。
c.L.Unlock()

好了,问题来了,调用 Wait 方法之前为什么要先加锁呢?

这是因为在我们使用共享资源的时候,可能一些代码是互斥的,所以我们需要加锁。 这样我们就可以保证在我们使用共享资源的时候,不会被其他协程修改。 但是如果因为条件不满足,我们需要等待的话,我们不可能在持有锁的情况下等待, 因为在修改条件的时候,可能也需要加锁,这样就会造成死锁。

另外一个问题是,为什么要使用 for 来检查条件是否满足,而不是使用 if 呢?

这是因为在我们调用 Wait 方法之后,可能会有其他协程唤醒我们,但是条件并没有满足, 这个时候依然是需要继续 Wait 的。

Signal 方法

Signal 方法用在当条件满足的时候,将 notifyList 中的第一个 goroutine 唤醒。

func (c *Cond) Signal() {
   // 检查 sync.Cond 是否被复制了
   c.checker.check()
   // 唤醒 notifyList 中的第一个 goroutine
   runtime_notifyListNotifyOne(&c.notify)
}

Broadcast 方法

Broadcast 方法用在当条件满足的时候,将 notifyList 中的所有 goroutine 唤醒。

func (c *Cond) Broadcast() {
   // 检查 sync.Cond 是否被复制了
   c.checker.check()
   // 唤醒 notifyList 中的所有 goroutine
   runtime_notifyListNotifyAll(&c.notify)
}

copyChecker 结构体

copyChecker 结构体用来检查 sync.Cond 是否被复制。它实际上只是一个 uintptr 类型的值。

type copyChecker uintptr

// check 方法检查 copyChecker 是否被复制了。
func (c *copyChecker) check() {
   if uintptr(*c) != uintptr(unsafe.Pointer(c)) &&
      !atomic.CompareAndSwapUintptr((*uintptr)(c), 0, uintptr(unsafe.Pointer(c))) &&
      uintptr(*c) != uintptr(unsafe.Pointer(c)) {
      panic("sync.Cond is copied")
   }
}

copyChecker 的值只有两种可能:

  • 0,表示还没有调用过 Wait, SignalBroadcast 方法。
  • uintptr(unsafe.Pointer(&copyChecker)),表示已经调用过 Wait, SignalBroadcast 方法。在这几个方法里面会调用 check 方法,所以 copyChecker 的值会被修改。

所以如果 copyChecker 的值不是 0,也不是 uintptr(unsafe.Pointer(&copyChecker))(也就是最初的 copyChecker 的内存地址),则表示 copyChecker 被复制了。

需要注意的是,这个方法在调用 CompareAndSwapUintptr 还会检查一下,这是因为有可能会并发调用 CompareAndSwapUintptr, 如果另外一个协程调用了 CompareAndSwapUintptr 并且成功了,那么当前协程的这个 CompareAndSwapUintptr 调用会返回 false, 这个时候就需要检查是否是因为另外一个协程调用了 CompareAndSwapUintptr 而导致的,如果是的话,就不会 panic

为什么 sync.Cond 不能被复制

从上一小节中我们可以看到,sync.Cond 其实是不允许被复制的,但是如果是在调用 Wait, SignalBroadcast 方法之前复制,那倒是没关系。

这是因为 sync.Cond 中维护了一个阻塞的 goroutine 列表。如果 sync.Cond 被复制了,那么这个列表就会被复制,这样就会导致两个 sync.Cond 都包含了这个列表;但是我们唤醒的时候,只会有其中一个 sync.Cond 被唤醒,另外一个 sync.Cond 就会一直阻塞。 所以 go 直接从语言层面限制了这种情况,不允许 sync.Cond 被复制。

总结

sync.Cond 是一个条件变量,它可以用来协调多个 goroutine 之间的同步,当条件满足的时候,去通知那些因为条件不满足被阻塞的 goroutine 继续执行。

sync.Cond 的接口比较简单,只有 Wait, SignalBroadcast 三个方法。

  • Wait 方法用来阻塞当前 goroutine,直到条件满足。调用 Wait 方法之前,需要先调用 L.Lock 方法加锁。
  • Signal 方法用来唤醒 notifyList 中的第一个 goroutine
  • Broadcast 方法用来唤醒 notifyList 中的所有 goroutine

sync.Cond 的实现也比较简单,它的核心就是 notifyList,它是一个链表,用来保存所有因为条件不满足而被阻塞的 goroutine

用关闭 channel 的方式也可以实现类似的广播功能,但是有个问题是 channel 不能被重复关闭,所以这种方式无法被多次使用。也就是说使用这种方式无法多次广播。

使用 channel 发送通知的方式也是可以的,但是这样实现起来就复杂很多了,就更容易出错了。

sync.Cond 中使用 copyChecker 来检查 sync.Cond 是否被复制,如果被复制了,就会 panic。需要注意的是,这里的复制是指调用了 WaitSignalBroadcast 方法之后,sync.Cond 被复制了。在调用这几个方法之前进行复制是没有影响的。

以上就是一文带你深入理解Go语言中的sync.Cond的详细内容,更多关于Go语言 sync.Cond的资料请关注我们其它相关文章!

(0)

相关推荐

  • Golang 中的 条件变量(sync.Cond)详解

    本篇文章面向的读者: 已经基本掌握Go中的 协程(goroutine),通道(channel),互斥锁(sync.Mutex),读写锁(sync.RWMutex) 这些知识.如果对这些还不太懂,可以先回去把这几个知识点解决了. 首先理解以下三点再进入正题: Go中的一个协程 可以理解成一个独立的人,多个协程是多个独立的人 多个协程都需要访问的 共享资源(比如共享变量) 可以理解成 多人要用的某种公共社会资源 上锁 其实就是加入到某个共享资源的争抢组中:上锁完成 就是从争抢组中被选出,得到了期待的

  • go并发编程sync.Cond使用场景及实现原理

    目录 使用场景 实现原理 copyChecker Wait Signal Broadcast 使用场景 sync.Cond是go标准库提供的一个条件变量,用于控制一组goroutine在满足特定条件下被唤醒. sync.Cond常用于一组goroutine等待,一个goroutine通知(事件发生)的场景.如果只有一个goroutine等待,一个goroutine通知(事件发生),使用Mutex或者Channel就可以实现. 可以用一个全局变量标志特定条件condition,每个sync.Con

  • Go并发编程sync.Cond的具体使用

    目录 简介 详细介绍 案例:Redis连接池 注意点 简介 Go 标准库提供 Cond 原语的目的是,为等待 / 通知场景下的并发问题提供支持.Cond 通常应用于等待某个条件的一组 goroutine,等条件变为 true 的时候,其中一个 goroutine 或者所有的 goroutine 都会被唤醒执行. Cond 是和某个条件相关,这个条件需要一组 goroutine 协作共同完成,在条件还没有满足的时候,所有等待这个条件的 goroutine 都会被阻塞住,只有这一组 goroutin

  • Go语言sync.Cond基本使用及原理示例详解

    目录 1. 简介 2. 基本使用 2.1 定义 2.2 方法说明 2.3 使用方式 2.4 使用例子 2.5 为什么Sync.Cond 需要关联一个锁,然后调用Wait方法前需要先获取该锁 3.使用场景 3.1 基本说明 3.2 场景说明 3.2.1 同步和协调多个协程之间共享资源 3.2.2 需要重复唤醒的场景中使用 4. 原理 4.1 基本原理 4.2 实现 4.2.1 Wait方法实现 4.2.2 Singal方法实现 4.2.3 Broadcast方法实现 5.使用注意事项 5.1 调用

  • GoLang并发编程中条件变量sync.Cond的使用

    目录 一.条件变量与互斥锁 二.条件变量与互斥锁的配合使用 三.条件变量的使用 创建锁和条件 使用 四.条件变量的Wait方法做了什么 一.条件变量与互斥锁 条件变量是基于互斥锁的,它必须基于互斥锁才能发挥作用: 条件变量并不是用来保护临界区和共享资源的,它是用来协调想要访问共享资源的那些线程的: 在Go语言中,条件变量最大的优势是效率方面的提升.当共享资源不满足条件的时候,想操作它的线程不用循环往返地检查了,只要等待通知就好了. 二.条件变量与互斥锁的配合使用 条件变量的初始化离不开互斥锁,并

  • 一文带你深入理解Go语言中的sync.Cond

    目录 sync.Cond 是什么 适用场景 sync.Cond 的基本用法 NewCond 创建实例 Wait 等待条件满足 Signal 通知一个等待的 goroutine Broadcast 通知所有等待的 goroutine sync.Cond 使用实例 为什么要用 sync.Cond close channel 广播实例 sync.Cond 基本原理 sync.Cond 的设计与实现 sync.Cond 模型 notifyList 结构体 sync.Cond 的方法 Wait 方法 Si

  • 一文带你深入探究Go语言中的sync.Map

    目录 1. Map 的基本实现原理 2. sync.Map 的实现原理 2.1 sync.Map 的结构体定义 2.2 sync.Map 的读取实现 2.3 sync.Map 的写入实现 2.4 sync.Map 的删除实现 2.5 sync.Map 的遍历实现 在 Go 语言中,有一个非常实用的并发安全的 Map 实现:sync.Map,它是在 Go 1.9 版本中引入的.相比于标准库中的 map,它的最大特点就是可以在并发情况下安全地读写,而不需要加锁.在这篇博客中,我们将深入探讨 sync

  • 一文带你彻底理解Java序列化和反序列化

    Java序列化是什么? Java序列化是指把Java对象转换为字节序列的过程,Java反序列化是指把字节序列恢复为Java对象的过程. 反序列化: 客户端重文件,或者网络中获取到文件以后,在内存中重构对象. 序列化: 对象序列化的最重要的作用是传递和保存对象的时候,保证对象的完整性和可传递性.方便字节可以在网络上传输以及保存在本地文件. 为什么需要序列化和反序列化 实现分布式 核心在于RMI,可以利用对象序列化运行远程主机上的服务,实现运行的时候,就像在本地上运行Java对象一样. 实现递归保存

  • 一文带你真正理解Java中的内部类

    目录 概述 内部类介绍和分类 常规内部类 局部内部类 匿名内部类 静态内部类 静态内部类和普通内部类的区别 内部类的作用 概述 不知道大家在平时的开发过程中或者源码里是否留意过内部类,那有思考过为什么要有内部类,内部类都有哪几种形式,静态内部类和普通内部类有什么区别呢?本篇文章主要带领大家理解下这块内容. 内部类介绍和分类 顾名思义,内部类是指一个类在另外一个类的内部,是定义在另一个类中的类.根据类的位置和属性不同,可以分为下面几种. 常规内部类 @Data public class Tree

  • 一文带你深入理解Linux中的nohup命令

    目录 前言 nohup是什么 nohup语法规则 nohup使用方法 后台运行命令 标准输出重定向到文件 标准错误输出重定向到文件 将标准输出和标准错误输出都重定向到文件 nohup后台进程管理 总结 前言 当我们在Linux或Unix系统上执行一个长时间运行的命令或脚本时,我们通常会遇到一个问题,那就是在终端关闭或者退出后,该进程也会随之停止运行.在这种情况下,我们需要一种方式来让进程在后台运行,而不受终端关闭的影响.这时,nohup命令就派上用场了. nohup是什么 nohup是Linux

  • 深入理解Go语言中的Dispatcher

    介绍 Go使用goroutines来处理connection的读写事件,不会阻塞: c, err := srv.newConn(rw) if err != nil { continue } go c.serve() c即为创建的connection,保存了该次请求的信息,然后再传递到对应的handler,handler就可以读取到请求的header信息,保证了请求之间独立. Go中的ServeMux 上面代码中提到了c(这个c就是connection).serve()方法.其实内部是调用了htt

  • 一文带你理解 Vue 中的生命周期

    目录 1.beforeCreate & created 2.beforeMount & mounted 3.beforeUpdate & updated 4.beforeDestroy & destroyed 5.activated & deactivated 前言: 每个 Vue 实例在被创建之前都要经过一系列的初始化过程.例如需要设置数据监听.编译模板.挂载实例到 DOM.在数据变化时更新 DOM 等.同时在这个过程中也会运行一些叫做生命周期钩子的函数,给予用户

  • 一文带你学会C语言中的qsort函数

    目录 铺垫知识 使用qsort函数进行整型数组的排序 使用qsort函数进行浮点型数组的排序 使用qsort函数进行结构体数组的排序 铺垫知识 qsort函数 参数类型 void qsort (void* base, size_t num, size_t size,int (*compar)(const void*,const void*)); 参数类型解释 参数1 待排序数组首元素的地址 参数2 数组内元素个数 参数3 数组内每个元素大小,单位是字节 参数4 函数指针,由自己实现,内容是两个元

  • 一文带你了解 C# DLR 的世界(DLR 探秘)

    在很久之前,我写了一片文章详解C# 匿名对象(匿名类型).var.动态类型 dynamic,可以借鉴.因为那时候是心中想当然的认为只有反射能够在运行时解析对象的成员信息并调用成员方法.后来也是因为其他的事一直都没有回过头来把这一节知识给补上,正所谓亡羊补牢,让我们现在来大致了解一下DLR吧. DLR 全称是 Dynamic Language Runtime(动态语言运行时).这很容易让我们想到同在C#中还有一个叫 CLR 的东西,它叫 Common Language Runtime.那这两者有什

  • 一文带你掌握Java8中Lambda表达式 函数式接口及方法构造器数组的引用

    目录 函数式接口概述 函数式接口示例 1.Runnable接口 2.自定义函数式接口 3.作为参数传递 Lambda 表达式 内置函数式接口 Lambda简述 Lambda语法 方法引用 构造器引用 数组引用 函数式接口概述 只包含一个抽象方法的接口,称为函数式接口. 可以通过 Lambda 表达式来创建该接口的对象. 可以在一个接口上使用 @FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口.同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口.

随机推荐