Go:方法
方法声明
type point struct { X, Y float64 }// 普通函数
func Distance(p, q Point) float64 {return math.Hypot(q.x - p.x, q.y - p.Y)
}// Point类型的方法
func (p Point) Distance(q Point) float64 {return math.Hypot(q.x - p.x, q.y - p.Y)
}
方法声明与普通函数声明类似,只是在函数名前多一个参数(接收者 ),将方法绑定到对应类型上 。以几何包为例 ,定义Point结构体 ,分别展示计算两点距离的普通函数Distance和Point类型的方法Distance ,方法的接收者p类似面向对象语言中向对象发送消息 ,Go 语言中接收者名字可自行选择 ,常用类型名首字母 。
p := Point{1, 2}
q := Point{4, 6}
fmt.Println(Distance(q)) // 函数调用
fmt.Println(p.Distance(q)) // 方法调用
- 调用方式:调用方法时,接收者在方法名前面 ,如
p.Distance(q),与声明保持一致 ,p.Distance这种表达式称作选择子 。
type Path []Pointfunc(path Path) Distance() float64 {sum := 0.0for i := range path {if i > 0 {sum += path[i-1].Distance(path[i])}}return sum
}
- 命名冲突:不同类型可使用相同方法名 ,如
Point和Path类型都有Distance方法 ,编译器根据方法名和接收者类型决定调用哪个 。在同一类型命名空间内 ,方法名不能与字段名冲突 。
类型与方法绑定
Go 语言可将方法绑定到多种类型上 ,不仅限于结构体类型 ,像Path这种命名的 slice 类型也能定义方法 。同一个包下 ,只要类型不是指针类型和接口类型 ,都可声明方法 。不同类型的同名方法彼此无关 ,如Path.Distance内部可能使用Point.Distance计算相邻点距离 。
指针接收者的方法
func (p *point) ScaleBy(factor float64) {p.x *= factorp.Y *= factor
}
当函数需更新变量,或实参过大想避免复制整个实参时,需用指针传递变量地址 。如(*Point).ScaleBy方法 ,用于按指定因子缩放Point结构体的坐标 ,其接收者为*Point指针类型 。
type P *int
func (P) f() { /*...*/ } // 编译错误:非法的接收者类型
- 声明规则:方法名是
(*Point).ScaleBy,括号必需 ,否则表达式会被错误解析 。习惯上若类型的一个方法使用指针接收者,其他方法也尽量用指针接收者 。命名类型(如Point)与其指针(*Point)是不同类型 ,不允许本身是指针的类型进行方法声明 。
r := &Point{1, 2}
r.ScaleBy(2)
fmt.Println(*r) // "{2, 4}"
// or
p := Point{1, 2}
pptr := &p
pptr.ScaleBy(2)
fmt.Println(p)
// or
p := Point{1, 2}
(&p).ScaleBy(2)
fmt.Println(p)
- 调用规则:可通过
*Point类型变量调用(*Point).ScaleBy方法 ,如r := &Point{1, 2}; r.ScaleBy(2)。若变量是Point类型,但方法要求*Point接收者 ,编译器会对变量进行&p的隐式转换 ,只有变量(包括结构体字段、数组或 slice 元素 )允许这种转换 ,不能对不能取地址的Point临时变量调用*Point方法 。
合法的方法调用表达式需符合以下三种形式:
- 实参接收者和形参接收者是同一类型 ,如
Point{1, 2}.Distance(q)(都是Point类型 ),pptr.ScaleBy(2)(都是*Point类型 )。 - 实参接收者是
T类型变量,形参接收者是*T类型 ,编译器会隐式获取变量地址 ,如p.ScaleBy(2)(p为Point类型 )。 - 实参接收者是
*T类型,形参接收者是T类型 ,编译器会隐式解引用接收者获取实际取值 ,如pptr.Distance(q)。
复制问题
若类型T方法接收者是T本身,调用方法时实参会被复制 ;若接收者是指针类型 ,应避免复制T实例 ,防止破坏内部数据 ,如bytes.Buffer实例复制会有问题 。
nil是一个合法的接收者
nil在自定义类型方法中的使用type IntList struct {Value intTail *IntList }func (list *IntList) Sum() int {if list == nil {return 0}return list.Value + List.Tail.Sum() }以整型数链表
IntList为例 ,*IntList类型中nil代表空链表 。Sum方法用于返回链表元素总和 ,当接收者list为nil时 ,直接返回 0 ,否则返回当前节点值与后续链表总和 。定义允许nil作为接收者的类型时 ,应在文档注释中明确标明 。
nil在标准库类型方法中的使用// Values 映射字符串到字符串列表 type Values map[string][]string// Get 返回第一个具有给定 key 的值 // 如不存在,则返回空字符串 func (v Values) Get(key string) string {if vs := v[key]; 1en(vs) > 0{ return vs[0] } return ""// Add 添加一个键值到对应 key 列表中 func (v Values) Add(key, value string){v[key] = append(v[key], value) }以
net/url包中的Values类型为例 ,它本质是映射字符串到字符串列表的map类型 ,提供Get和Add等方法 。Get方法返回指定键的第一个值 ,若键不存在或接收者为nil,返回空字符串 ;Add方法向对应键列表添加值 。当Values类型接收者为nil时 ,Get方法可正常工作 ,但Add方法会宕机 ,因为尝试更新一个空map。方法对接收者引用本身的改变(如设置为nil或指向不同map)不会影响调用者 。
通过结构体内嵌组成类型
type Point struct{ X, Y float64 }
type ColordPoint struct {PointColor color.RGBA
}var cp ColoredPoint
cp.X = 1
fmt.Println(cp.Point.X)
cp.Point.Y = 2
fmt.Println(cp.Y)red := color.RGBA{255, 0, 0, 255}
bule := color.RGBA{0, 0, 255, 255}
var p = ColoredPoint{Point{1, 1}, red}
var q = ColoredPoint{Point{5, 4}, blue}
fmt.Println(p.Distance(q.Point)) // "5"
q.ScaleBy(2)
q.ScaleBy(2)
fmt.Println(p.Distance(q.Point)) // "10"
以ColoredPoint类型为例 ,它嵌套了Point结构体 ,并包含Color字段 。通过嵌套 ,ColoredPoint可直接使用Point的字段 ,如cp.X等同于cp.Point.X ,也能调用Point类型的方法 ,如p.Distance(q.Point) ,实现代码相当于自动生成了包装方法来调用Point声明的方法 。但要注意ColoredPoint不是Point ,不能直接传递ColoredPoint实例给要求Point参数的方法 。
type coloredPoint struct {*PointColor color.RGBA
}
p := ColoredPoint{&Point{1, 1}, red}
q := ColoredPoint{&Point{5, 4}, blue}
fmt.Println(p.Distance(*q.Point)) // "5"
q.Point = p.Point
p.ScaleBy(2)
fmt.Println(*p.Point, *q.Point) // "{2 2} {2 2}"
当ColoredPoint嵌套*Point指针类型时 ,字段和方法间接地来自所指向的对象 ,如p和q可共享一个Point 。结构体类型可拥有多个嵌套字段 ,编译器处理选择子(如p.ScaleBy )时 ,优先查找直接声明的方法 ,再依次从嵌套字段的方法中查找 。
var (mu sync.Mutexmapping = make(map[string]string)
)func Lookup(key string) string {mu.Lock()v := mapping[key]mu.Unlock()return v
}
var cache = struct {sync.Mutexmapping map[string]string
} {mapping: make(map[string]string),
}func Lookup(key string) string {cache.Lock()v := cache.mapping[key]cache.Unlock()return v
}
结构体嵌套在缓存实现中的应用,最初使用包级别的互斥锁mu和mapping变量保护map数据 ,后来将相关变量封装到cache结构体中 ,该结构体嵌套了sync.Mutex ,这样cache变量可直接使用Mutex的Lock和Unlock方法进行加锁和解锁操作 ,使代码结构更清晰 。
方法变量与表达式
p := Point{1, 2}
q := Point{4, 6}
distanceFromP := p.Distance // 方法变量
fmt.Println(ditanceFromP(q))
var origin Point
fmt.Println(distanceFromP(origin))scaleP := p.ScaleBy // 方法变量
scaleP(2) // p -> (2, 4)
scaleP(3) // (6, 12)
scaleP(10) // (60, 120)
- 可将选择子(如
p.Distance)赋值给一个变量 ,形成方法变量 ,它是一个函数 ,绑定了方法(Point.Distance)和接收者p。调用时只需提供实参 ,无需再提供接收者 。
type Rocket struct { /*...*/ }
func (r *Rocket) Launch { /*...*/ }r := new(Rocket) Launch { /*...*/ }
// time.AfterFunc(10 * time.Second, func() { r. Launch() })
time.AfterFunc(10 * time.Second, r.Launch)
- 在
time.AfterFunc等场景中 ,方法变量很有用 。如启动火箭的例子 ,使用方法变量可使代码更简洁 ,直接传递r.Launch给time.AfterFunc,在延迟后调用该方法 。
方法表达式
- 方法表达式写成
T.f或(*T).f形式 ,其中T是类型 ,它把方法的接收者替换成函数的第一个形参 ,可像普通函数一样调用 。同样以Point结构体的Distance和ScaleBy方法为例 ,展示方法表达式的赋值与调用 。
type Point struct{ X, Y float64 }func (p Point) Add(q Point) Point { return Point{p.X + q.X, p.Y + q.Y} }
func (p Point) Sub(q Point) Point { return Point{p.X - q.X, p.Y - q.Y} }type Path []Pointfunc (path Path) TranslateBy(offset Point, add bool) {var op func(p, q Point) Pointif add {op = Point.Add} else {op = Point.Sub}for i := range path {// 调用 path[i].Add(offset) 或者是 path[i].Sub(offset)path[i] = op(path[i], offset)}
}
当需要用一个值代表同一类型的多个方法 ,并处理不同接收者时 ,方法变量很有帮助 。如Path.TranslateBy函数 ,根据add参数决定使用Point.Add或Point.Sub方法 ,对路径上的每个点进行相应计算 。
示例:位向量
在数据分析领域,对于元素为小的非负整型且元素众多,操作多为求并集和交集的集合,使用map[T]bool实现集合扩展性虽好但性能欠佳,位向量是更优数据结构 。
// IntSet 是一个包含非负整数的集合
// 零值代表空的集合
type IntSet struct {words []uint64
}
// Has 方法的返回值表示是否存在非负数x
func(s *IntSet) Has(x int) bool{word, bit := x/64, uint(x%64) return word < len(s.words) && s.words[word]&(1<<bit)!=0
}
// Add 添加非负数 x 到集合中
func(s *IntSet) Add(x int){word, bit := x/64, uint(x%64) for word >= 1en(s.words){s.word s = append(s.words, 0)}s.words[word] |= 1<<bit
}// Unionwith 将会对 s 和 t 做并集并将结果存在 s 中
func(s *ntSet) Unionwith(t *IntSet){for i, tword := range t.words { if i < len(s.words){s.words[1] |= tword} else { s.words = append(s.words, tword)}
}// String方法以字符串"{1 2 3}"的形式返回集中
func (s *IntSet) String() string {var buf bytes.Bufferbuf.WriteByte('{')for i, word := range s.words {if word == 0 {continue}for j := 0; j < 64; j++ {if word&(1<<uint(j)) != 0 {if buf.Len() > len("{") {buf.WriteByte(' ')}fmt.Fprintf(&buf, "%d", 64*i+j)}}}buf.WriteByte('}')return buf.String()
}
IntSet类型的实现与方法
- 结构定义:
IntSet结构体包含words []uint64字段 ,用无符号整型值的 slice 表示集合 ,每一位代表集合中的一个元素 。 - 方法功能:
Has方法:判断集合中是否存在非负数x,通过计算x所在的字索引和位索引 ,检查对应位是否为 1 。Add方法:向集合中添加非负数x,确定x所在字索引 ,若字不存在则扩展words,然后将对应位置为 1 。UnionWith方法:对两个IntSet求并集 ,遍历另一个集合的字 ,与当前集合对应字按位或操作 ,不存在的字添加到当前集合 。String方法:以字符串形式输出集合元素 ,使用bytes.Buffer,遍历words,对每个字的每一位检查 ,是 1 则将对应元素添加到字符串 。
IntSet类型方法声明为指针类型接收者 ,使用值调用方法时需注意 ,编译器会隐式插入&操作符获取指针以调用String方法 ,若无String方法 ,fmt.Println会直接输出结构体 。
封装
- 概念:封装(数据隐藏 )是面向对象编程重要方面,指变量或方法不能通过对象访问 。
- 实现方式:Go 语言通过标识符首字母大小写控制命名可见性 ,首字母大写可从包中导出 ,首字母小写则不导出 ,要封装对象需使用结构体 。以
IntSet类型为例 ,若定义为结构体且字段words首字母小写 ,则该字段在包外不可见 ;若重新定义IntSet为 slice 类型 ,表达式*s可在其他包内使用 ,但会暴露内部表示 。Go 语言中封装单元是包而非类型 ,结构体字段在同包内代码可见 。
优点
- 减少变量检查:使用方不能直接修改对象变量 ,减少检查变量值的代码 。
type Buffer struct {buf []byteinitial [64]byte/*... */
}// Grow方法按需扩展缓冲区的大小
// 保证n个字节的空间
func (b *Buffer) Grow(n int) {if b.buf == nil {b.buf = b.initial[:0] // 最初使用预分配的空间}if len(b.buf)+n > cap(b.buf) {buf := make([]byte, b.Len(), 2*cap(b.buf)+n)copy(buf, b.buf)b.buf = buf}
}
- 隐藏实现细节:防止使用方依赖的属性改变 ,方便设计者灵活改变 API 实现且不破坏兼容性 。以
bytes.Buffer为例 ,其内部字段未导出 ,外部使用者无需关心实现细节 ,仅感知性能提升 。
type Counter struct { n int }func (c *Counter) N() int { return c.n }
func (c *Counter) Increment() { c.n++ }
func (c *Counter) Reset() { c.n = 0 }
- 保护对象内部资源:防止使用者随意改变对象内变量 ,包作者可通过包内函数维护对象内部资源 。如
Counter类型 ,使用者只能通过特定方法递增或重置计数器 ,不能随意设置计数值 。
导出字段与封装的权衡
Go 语言允许导出字段 ,但需慎重考虑 API 兼容性、维护复杂度等因素 。同时封装并非总是必需 ,如time.Duration类型暴露int64整型用于运算 。
参考资料:《Go程序设计语言》
相关文章:
Go:方法
方法声明 type point struct { X, Y float64 }// 普通函数 func Distance(p, q Point) float64 {return math.Hypot(q.x - p.x, q.y - p.Y) }// Point类型的方法 func (p Point) Distance(q Point) float64 {return math.Hypot(q.x - p.x, q.y - p.Y) }方法声明与普通函数声…...
深入剖析 Axios 的 POST 请求:何时使用 qs 处理数据
在前端开发中,Axios 是一个广泛使用的用于发送 HTTP 请求的库,特别是在处理 POST 请求时,数据的处理方式会直接影响到请求能否正确被后端接收和处理。其中,使用 qs 库对数据进行处理是一个常见的操作点,本文将深入探讨…...
【数据结构_7】栈和队列(上)
一、概念 栈和队列,也是基于顺序表和链表实现的 栈是一种特殊的线性表,其只允许在固定的一段进行插入和删除元素操作。 遵循后进先出的原则 此处所见到的栈,本质上就是一个顺序表/链表,但是,实在顺序表/链表的基础…...
Linux》》bash 、sh 执行脚本
通常使用shell去运行脚本,两种方法 》bash xxx.sh 或 bash “xxx.sh” 、sh xxx.sh 或 sh “xxx.sh” 》bash -c “cmd string” 引号不能省略 我们知道 -c 的意思是 command,所以 bash -c 或 sh -c 后面应该跟一个 command。...
如何用“AI敏捷教练“破解Scrum项目中的“伪迭代“困局?
一、什么是“伪迭代”? “伪迭代”是指团队表面上采用Scrum框架,但实际运作仍沿用瀑布模式的现象。例如:迭代初期开发人员集中编码、末期测试人员突击测试,导致资源分配不均;需求拆分粗糙,团队无法在固定时…...
使用 vxe-table 来格式化任意的金额格式,支持导出与复制单元格格式到 excel
使用 vxe-table 来格式化任意的金额格式,支持导出与复制单元格格式到 excel 查看官网:https://vxetable.cn gitbub:https://github.com/x-extends/vxe-table gitee:https://gitee.com/x-extends/vxe-table 安装 npm install vx…...
金币捕鱼类手游《海洋管家》源码结构与系统分层解析
在休闲互动类移动应用开发中,捕鱼类项目因玩法成熟、逻辑清晰而成为不少开发者接触多端架构与模块化管理的重要起点。本文以一款名为《海洋管家》的项目源码为样例,简要解析其整体结构与主要功能模块,供有类似项目需求或系统学习目的的开发者…...
Go语言实现OAuth 2.0认证服务器
文章目录 1. 项目概述1.1 OAuth2 流程 2. OAuth 2.0 Storage接口解析2.1 基础方法2.2 客户端管理相关方法2.3 授权码相关方法2.4 访问令牌相关方法2.5 刷新令牌相关方法 2.6 方法调用时序2.7 关键注意点3. MySQL存储实现原理3.1 数据库设计3.2 核心实现 4. OAuth 2.0授权码流程…...
【2025年认证杯数学中国数学建模网络挑战赛】C题 数据预处理与问题一二求解
目录 【2025年认证杯数学建模挑战赛】C题数据预处理与问题一求解三、数据预处理及分析3.1 数据可视化3.2 滑动窗口相关系数统计与动态置信区间耦合分析模型3.3 耦合关系分析结果 四、问题一代码数据预处理问题一 【2025年认证杯数学建模挑战赛】C题 数据预处理与问题一求解 三…...
2025年最新Web安全(面试题)
活动发起人小虚竹 想对你说: 这是一个以写作博客为目的的创作活动,旨在鼓励大学生博主们挖掘自己的创作潜能,展现自己的写作才华。如果你是一位热爱写作的、想要展现自己创作才华的小伙伴,那么,快来参加吧!…...
利用Global.asax在ASP.NET Web应用中实现功能
Global.asax文件(也称为ASP.NET应用程序文件)是ASP.NET Web应用程序中的一个重要文件,它允许您处理应用程序级别和会话级别的事件。下面介绍如何利用Global.asax来实现各种功能。 Global.asax基本结构 <% Application Language"C#&…...
开源微调混合推理模型:cogito-v1-preview-qwen-32B
一、模型概述 1.1 模型特点 Cogito v1-preview-qwen-32B 是一款基于指令微调的生成式语言模型(LLM),具有以下特点: 支持直接回答(标准模式)和自我反思后再回答(推理模式)。使用 I…...
Golang|Channel 相关用法理解
文章目录 用 channel 作为并发小容器channel 的遍历channel 导致的死锁问题用 channel 传递信号用 channel 并行处理文件用channel 限制接口的并发请求量用 channel 限制协程的总数量 用 channel 作为并发小容器 注意这里的 ok 如果为 false,表示此时不仅channel为空…...
C++ - #命名空间 #输入、输出 #缺省参数 #函数重载 #引用 # const 引用 #inline #nullptr
文章目录 前言 一、实现C版本的hello world 二、命名空间 1、namespace 的价值 2、namespace 的定义 (1.域会影响一个编译器编译语法时的查找规则 (2、域会影响生命周期 (3、命名空间域只能定义在全局 (4、编译器会自动合并相同命名空间中的内容 (5、C标准库放在命名…...
Spring Boot 应用程序中配置使用consul
配置是 Spring Boot 应用程序中的一部分,主要用于配置服务端口、应用名称、Consul 服务发现以及健康检查等功能。以下是对每个部分的详细解释: 1. server.port server:port: 8080作用:指定 Spring Boot 应用程序运行的端口号。解释…...
JSON处理工具/框架的常见类型及详解,以Java语言为例
以下是JSON处理工具/框架的常见类型及详解,以Java语言为例: 一、主流JSON处理工具对比 Jackson(推荐) 特点:高性能、功能丰富,支持注解(如JsonProperty)、树形模型(Json…...
4. k8s核心概念 pod deployment service
以下是 Kubernetes 的核心概念详解,涵盖 Pod、Service、Deployment 和 Node,以及它们之间的关系和实际应用场景: 1. Pod 定义与作用 • 最小部署单元:Pod 是 Kubernetes 中可创建和管理的最小计算单元,包含一个或多个…...
c++中max函数怎么使用?
在C中,std::max函数是一个在 <algorithm> 标准库头文件中定义的函数模板,用于确定两个或更多个数值之间的最大值。下面是对std::max函数的基本介绍和使用示例: 函数原型: template <class T> constexpr const T&…...
使用Redis实现分布式限流
一、限流场景与算法选择 1.1 为什么需要分布式限流 在高并发系统中,API接口的突发流量可能导致服务雪崩。传统的单机限流方案在分布式环境下存在局限,需要借助Redis等中间件实现集群级流量控制。 1.2 令牌桶算法优势 允许突发流量:稳定速…...
中间件--ClickHouse-1--基础介绍(列式存储,MPP架构,分布式计算,SQL支持,向量化执行,亿万级数据秒级查询)
1、概述 ClickHouse是一个用于联机分析(OLAP)的列式数据库管理系统(DBMS)。它由俄罗斯的互联网巨头Yandex为解决其内部数据分析需求而开发,并于2016年开源。专为大规模数据分析,实时数据分析和复杂查询设计,具有高性能、实时数据和可扩展性等…...
Java中的经典排序算法:插入排序、希尔排序、选择排序、堆排序与冒泡排序(如果想知道Java中有关插入排序、希尔排序、选择排序、堆排序与冒泡排序的知识点,那么只看这一篇就足够了!)
前言:排序算法是计算机科学中的基础问题之一,它在数据处理、搜索算法以及各种优化问题中占有重要地位,本文将详细介绍几种经典的排序算法:插入排序、选择排序、堆排序和冒泡排序。 ✨✨✨这里是秋刀鱼不做梦的BLOG ✨✨✨想要了解…...
K8S+Prometheus+Consul+alertWebhook实现全链路服务自动发现与监控、告警配置实战
系列文章目录 k8s服务注册到consul prometheus监控标签 文章目录 系列文章目录前言一、环境二、Prometheus部署1.下载2.部署3.验证 三、kube-prometheus添加自定义监控项1.准备yaml文件2.创建新的secret并应用到prometheus3.将yaml文件应用到集群4.重启prometheus-k8s pod5.访…...
uniapp-商城-25-顶部模块高度计算
计算高度: 使用computed进行顶部模块的计算。 总高度:bartotalHeight log 介绍--收款码这一条目 也就是上一章节的title的高度计算 bodybarheight。 在该组件中: js部分的代码: 包含了导出的名字: shop-head…...
Proxmox VE 网络配置命令大全
如果对 Proxmox VE 全栈管理感兴趣,可以关注“Proxmox VE 全栈管理”专栏,后续文章将围绕该体系,从多个维度深入展开。 概要:Proxmox VE 网络配置灵活,满足虚拟化组网需求。基础靠桥接实现虚拟机与物理网络互联&#x…...
非关系型数据库(NoSQL)与 关系型数据库(RDBMS)的比较
非关系型数据库(NoSQL)与 关系型数据库(RDBMS)的比较 一、引言二、非关系型数据库(NoSQL)2.1 优势 三、关系型数据库(RDBMS)3.1 优势 四、结论 💖The Begin💖…...
WPF 图标原地旋转
如何使元素原地旋转 - WPF .NET Framework | Microsoft Learn <ButtonRenderTransformOrigin"0.5,0.5"HorizontalAlignment"Left">Hello,World<Button.RenderTransform><RotateTransform x:Name"MyAnimatedTransform" Angle"…...
蓝桥杯2024国B数星星
小明正在一棵树上数星星,这棵树有 n 个结点 1,2,⋯,n。他定义树上的一个子图 G 是一颗星星,当且仅当 G 同时满足: G 是一棵树。G 中存在某个结点,其度数为 ∣VG∣−1。其中 ∣VG∣ 表示这个子图含有的结点数。 两颗星星不相…...
Ubuntu 系统上通过终端安装 Google Chrome 浏览器
使用终端安装前,需要配置好终端使用了代理。 参考文章:https://blog.csdn.net/yangshuo1281/article/details/147262633?spm1011.2415.3001.5331 转自 风车 首先,添加 Google Chrome 的软件源和密钥: # 下载并添加 Google 的签…...
中科院1区顶刊Expert Systems with Applications ESO:增强型蛇形算法,性能不错
Snake Optimizer(SO)是一种优化效果良好的新颖算法,但由于自然规律的限制,在探索和开发阶段参数更多是固定值,因此SO算法很快陷入局部优化并慢慢收敛。本文通过引入新颖的基于对立的学习策略和新的动态更新机制&#x…...
zk(Zookeeper)实现分布式锁
Zookeeper实现分布式锁 1,zk中锁的种类: 读锁:大家都可以读,要想上读锁的前提:之前的锁没有写锁 写锁:只有得到写锁的才能写。要想上写锁的前提是:之前没有任何锁 2,zk如何上读锁 创…...
