~目录~
Golang 基于etcd的分布式锁 - 公平锁和非公平锁
Golang 学习笔记
目的
首先,要确认好希望实现的东西
- 希望实现一个分布式锁
- 可以实现公平锁和非公平锁
- 使用起来不复杂
设想
- Key :钥匙
package qtlock // Key 钥匙 type Key string
- Lock :锁
package qtlock // Lock 锁 type Lock interface { // Unlock 可以直接调用锁的方法来释放锁 Unlock() }
- Locker :负责上锁和解锁,区分公平和非公平
package qtlock // Locker type Locker interface { // TryLock 尝试获取锁,不会等待,直接返回获取结果 // 如果成功,返回 lock,nil // 如果失败,返回 nil,nil // 如果出错,返回 nil,err TryLock(key Key) (lock Lock, err error) // Unlock 释放锁 Unlock(lock Lock) // Lock 获取锁 // 等待获取锁或者ctx结束 Lock(ctx context.Context, key Key) (lock Lock, err error) } // GetLocker 获取一个Locker // Locker会根据 fair 的值去实现公平或者非公平的抢占锁行为 func GetLocker(fair bool) Locker
- 调用方式:
func main() { wg := &sync.WaitGroup{} wg.Add(4) startTime := time.Now() locker := qtlock.GetLocker(true) for i := range []int{1, 2, 3, 4} { go func(i int) { defer wg.Done() //如果10秒钟还没有获取锁,则放弃 ctx, cf := context.WithTimeout(context.Background(), 10*time.Second) defer cf() if lock, err := locker.Lock(ctx, "my-key"); err != nil { fmt.Println(i, "err", err) } else if lock != nil { defer func() { lock.Unlock() fmt.Println(i, "释放锁") }() fmt.Println(i, "获取锁", time.Now().Sub(startTime).Seconds()) time.Sleep(4 * time.Second) } else { fmt.Println(i, "没有获取锁", time.Now().Sub(startTime).Seconds()) } }(i) } wg.Wait() fmt.Println("所有工作都完成啦", time.Now().Sub(startTime).Seconds()) }
具体实现
1. 首先把接口都定义好
package qtlock
import "context"
// Key 钥匙
type Key string
// Lock 锁
type Lock interface {
// Unlock 可以直接调用锁的方法来释放锁
Unlock()
}
// Locker 锁柜
type Locker interface {
// TryLock 尝试获取锁,不会等待,直接返回获取结果
// 如果成功,返回 lock,nil
// 如果失败,返回 nil,nil
// 如果出错,返回 nil,err
TryLock(key Key) (lock Lock, err error)
// Unlock 释放锁
Unlock(lock Lock)
// Lock 获取锁
// 等待获取锁或者ctx结束
Lock(ctx context.Context, key Key) (lock Lock, err error)
}
// LockerProvider Locker的提供者
type LockerProvider func(fair bool) Locker
var lockProvider LockerProvider
// GetLocker 获取一个锁
func GetLocker(fair bool) Locker {
if lockProvider == nil {
return nil
}
return lockProvider(fair)
}
// RegisterLockerProvider 注册一个LockerProvider
func RegisterLockerProvider(provider LockerProvider) {
lockProvider = provider
}
2. etcd 实现
1. 存储的KV方案
我们需要将key存入etcd中,表示当前正锁住了
在公平锁的模式下,我们还需要将抢占者以队列的方式保存在etcd中
以 key :test-key 为例子
抢占的锁的Key:Value:
/qt/lock/test-key : id0
抢占者队列Key:Value:
/qt/lock/test-key/_OWNERS_/idA : idA
/qt/lock/test-key/_OWNERS_/idB : idB
这样,我们可以通过查询
前缀=/qt/lock/test-key/_OWNERS_/
排序=CreateRevision 升序
来查到最先的等待者,以此来判断自己是否可以进行抢占锁了
2. 租约方案
对于 Key:
- 我们需要租约的方式,给key一个过期时间,防止程序或者机器意外,导致key无法清除,即锁无法释放
- 在锁没有被持有期间,需要不停的进行续租,防止key过期后删除,导致锁被别人拿了
- 在锁被释放后,需要马上废除租约,并且关闭续租。使得其他人可以立即开始抢占锁
对于等待者队列的OwnerKey
- 需要租约的方式,防止程序或机器意外后,无法清楚key,使得后续排队的Owner一直排队下去
- 排队期间需要保持续租,防止掉队
- 持有期间也可保持续租
- 锁被释放后,需要马上废除租约,并且关闭续租。使得队列后面的人立即开始抢占
3. TryLock 非公平
非公平锁的模式下,抢占锁无需考虑自己之前是否已经有人在等待了,直接抢的就完事了
由于无需阻塞,如果没有抢到,直接返回 nil 就行了
func (l *Locker) tryLock(key qtlock.Key, uuid uuid.UUID) (lock qtlock.Lock, err error) {
newLock := &Lock{
locker: l,
key: genKey(key), // /qt/lock/key
uuid: uuid,
}
// 创建自动续约的租约
cxt, cancelLeaseFun := context.WithCancel(context.Background())
defer func() {
//如果返回的lock=nil说明创建没成功,调下取消续租的方法
if lock == nil {
cancelLeaseFun()
}
}()
if lease, err := createKeepAliveLease(cxt, 10, l.client); err != nil {
return nil, err
} else {
//保存租约和取消租约的方法
newLock.lease = lease
newLock.leaseCancelFun = cancelLeaseFun
}
//尝试抢锁
txn := l.client.Txn(context.Background())
// 如果没有锁则抢占锁,不然就获取当前的锁
txn.If(clientv3.Compare(clientv3.CreateRevision(newLock.key), "=", 0)).
Then(clientv3.OpPut(newLock.key, newLock.uuid.String(), clientv3.WithLease(newLock.lease))).
Else(clientv3.OpGet(newLock.key))
if resp, err := txn.Commit(); err != nil {
return nil, err
} else {
if resp.Succeeded {
return newLock, nil
} else {
return nil, nil
}
}
}
2. TryLock 公平
TryLock下,其实不需要进行等待,只需要关心目前队列中是否有在等待的抢占者就行,如果有就不去抢占
所以只需要在上面非公平的模式下添加一个前置判断
func (l *Locker) tryLock(key qtlock.Key, uuid uuid.UUID) (lock qtlock.Lock, err error) {
newLock := &Lock{
locker: l,
key: genKey(key), // /qt/lock/key
uuid: uuid,
}
if l.fair {
//获取锁下的owner节点数量,有就返回
if response, err := l.client.Get(context.Background(),
newLock.key+"/_OWNERS_/", clientv3.WithPrefix(),
clientv3.WithSort(clientv3.SortByCreateRevision, clientv3.SortAscend),
clientv3.WithLimit(1),
); err != nil {
return nil, err
} else {
if len(response.Kvs) == 1 && string(response.Kvs[0].Value) != newLock.uuid.String() {
return nil, nil
}
}
}
...
}
3. Lock 非公平
Lock阻塞模式下,需要对Key进行watch,如果发现Key被删除了,说明锁被释放了,然后进行抢占
func (l *Locker) Lock(ctx context.Context, key qtlock.Key) (lock qtlock.Lock, err error) {
id := uuid.New()
//创建watch
cancelCtx, cancelFun := context.WithCancel(context.TODO())
//方法结束说明不需要再watch了
defer cancelFun()
//watch Key的事件
watch := l.client.Watch(cancelCtx, genKey(key))
//首先尝试一波是否可以抢占,可以就返回lock
if lock, err := l.tryLock(key, id); err != nil {
return nil, err
} else if lock != nil {
return lock, nil
}
//select模式,如果超时了就不干了
for {
select {
case resp := <-watch:
for _, ev := range resp.Events {
if ev.Type == clientv3.EventTypeDelete {
if lock, err := l.tryLock(key, id); err != nil {
return nil, err
} else if lock != nil {
return lock, nil
}
}
}
case <-ctx.Done():
return nil, ctx.Err()
}
}
}
4. Lock 公平
Lock的公平模式下,还需要在队列里面添加抢占者,同时watch的时候需要watch抢占者列表。
如果抢占者被删除了,我们也需要再尝试去抢占
对于非公平的方法进行修改,就可以实现了
func (l *Locker) Lock(ctx context.Context, key qtlock.Key) (lock qtlock.Lock, err error) {
id := uuid.New()
var ownerLease clientv3.LeaseID
var ownerCancelFunc func()
defer func() {
if lock != nil {
etcdLock := lock.(*Lock)
etcdLock.ownerCancelFun = ownerCancelFunc
etcdLock.ownerLease = ownerLease
} else {
if ownerCancelFunc != nil {
ownerCancelFunc()
}
if ownerLease != 0 {
l.client.Lease.Revoke(context.Background(), ownerLease)
}
}
}()
if l.fair {
cancelCtx, cancelFun := context.WithCancel(context.Background())
if leaseID, err := createKeepAliveLease(cancelCtx, 10, l.client); err != nil {
cancelFun()
return nil, err
} else {
ownerLease = leaseID
ownerCancelFunc = cancelFun
}
if _, err := l.client.Put(context.Background(), genKey(key)+"/_OWNERS_/"+id.String(), id.String(), clientv3.WithLease(ownerLease)); err != nil {
return nil, err
}
}
//创建watch
cancelCtx, cancelFun := context.WithCancel(context.Background())
//方法结束说明不需要再watch了
defer cancelFun()
watch := l.client.Watch(cancelCtx, genKey(key), clientv3.WithPrefix())
//首先尝试一波是否可以抢占,可以就返回lock
if lock, err := l.tryLock(key, id); err != nil {
return nil, err
} else if lock != nil {
return lock, nil
}
//select模式,如果超时了就不干了
for {
select {
case resp := <-watch:
for _, ev := range resp.Events {
if ev.Type == clientv3.EventTypeDelete {
if lock, err := l.tryLock(key, id); err != nil {
return nil, err
} else if lock != nil {
return lock, nil
}
}
}
case <-ctx.Done():
return nil, ctx.Err()
}
}
}
5. 最终etcd实现代码
package qtlock_etcd
import (
"context"
"github.com/google/uuid"
clientv3 "go.etcd.io/etcd/client/v3"
"qtjob/qtlock"
)
var defaultClient *clientv3.Client
func init() {
var err error
if defaultClient, err = clientv3.New(clientv3.Config{Endpoints: []string{"127.0.0.1:2379"}}); err != nil {
panic(err)
}
qtlock.RegisterLockerProvider(getLocker)
}
// getLocker 获取Locker
func getLocker(fair bool) qtlock.Locker {
return &Locker{
client: defaultClient,
fair: fair,
}
}
//创建自动续租的租约
func createKeepAliveLease(ctx context.Context, ttl int64, client *clientv3.Client) (leaseId clientv3.LeaseID, err error) {
//创建租约
if grant, err := client.Lease.Grant(context.Background(), ttl); err != nil {
return 0, err
} else {
leaseId = grant.ID
}
//自动续租
if err := keepLeaseAlive(ctx, leaseId, client); err != nil {
return 0, err
}
return leaseId, nil
}
func keepLeaseAlive(ctx context.Context, leaseId clientv3.LeaseID, client *clientv3.Client) error {
if _, err := client.Lease.KeepAlive(ctx, leaseId); err != nil {
return err
} else {
return nil
}
}
type Lock struct {
locker *Locker
key string
uuid uuid.UUID
lease clientv3.LeaseID
leaseCancelFun func()
ownerLease clientv3.LeaseID
ownerCancelFun func()
}
func (l *Lock) Unlock() {
l.locker.Unlock(l)
}
type Owner struct {
}
type Locker struct {
fair bool //是否公平
client *clientv3.Client //etcd锁的客户端
}
func (l *Locker) TryLock(key qtlock.Key) (lock qtlock.Lock, err error) {
return l.tryLock(key, uuid.New())
}
func (l *Locker) tryLock(key qtlock.Key, uuid uuid.UUID) (lock qtlock.Lock, err error) {
newLock := &Lock{
locker: l,
key: genKey(key),
uuid: uuid,
}
if l.fair {
//获取锁下的owner节点数量,有就返回
if response, err := l.client.Get(context.Background(),
newLock.key+"/_OWNERS_/", clientv3.WithPrefix(),
clientv3.WithSort(clientv3.SortByCreateRevision, clientv3.SortAscend),
clientv3.WithLimit(1),
); err != nil {
return nil, err
} else {
if len(response.Kvs) == 1 && string(response.Kvs[0].Value) != newLock.uuid.String() {
return nil, nil
}
}
}
// 创建自动续约的租约
cxt, cancelLeaseFun := context.WithCancel(context.Background())
defer func() {
//如果返回的lock=nil说明创建没成功,调下取消续租的方法
if lock == nil {
cancelLeaseFun()
}
}()
if lease, err := createKeepAliveLease(cxt, 10, l.client); err != nil {
return nil, err
} else {
//保存租约和取消租约的方法
newLock.lease = lease
newLock.leaseCancelFun = cancelLeaseFun
}
//尝试抢锁
txn := l.client.Txn(context.Background())
// 如果没有锁则抢占锁,不然就获取当前的锁
txn.If(clientv3.Compare(clientv3.CreateRevision(newLock.key), "=", 0)).
Then(clientv3.OpPut(newLock.key, newLock.uuid.String(), clientv3.WithLease(newLock.lease))).
Else(clientv3.OpGet(newLock.key))
if resp, err := txn.Commit(); err != nil {
return nil, err
} else {
if resp.Succeeded {
return newLock, nil
} else {
return nil, nil
}
}
}
func (l *Locker) Unlock(lock qtlock.Lock) {
if etcdLock, ok := lock.(*Lock); ok {
if l.fair {
if etcdLock.ownerCancelFun != nil {
etcdLock.ownerCancelFun()
}
if etcdLock.ownerLease != 0 {
l.client.Lease.Revoke(context.Background(), etcdLock.ownerLease)
}
}
etcdLock.leaseCancelFun()
l.client.Lease.Revoke(context.Background(), etcdLock.lease)
}
}
func (l *Locker) Lock(ctx context.Context, key qtlock.Key) (lock qtlock.Lock, err error) {
id := uuid.New()
var ownerLease clientv3.LeaseID
var ownerCancelFunc func()
defer func() {
if lock != nil {
etcdLock := lock.(*Lock)
etcdLock.ownerCancelFun = ownerCancelFunc
etcdLock.ownerLease = ownerLease
} else {
if ownerCancelFunc != nil {
ownerCancelFunc()
}
if ownerLease != 0 {
l.client.Lease.Revoke(context.Background(), ownerLease)
}
}
}()
if l.fair {
cancelCtx, cancelFun := context.WithCancel(context.Background())
if leaseID, err := createKeepAliveLease(cancelCtx, 10, l.client); err != nil {
cancelFun()
return nil, err
} else {
ownerLease = leaseID
ownerCancelFunc = cancelFun
}
if _, err := l.client.Put(context.Background(), genKey(key)+"/_OWNERS_/"+id.String(), id.String(), clientv3.WithLease(ownerLease)); err != nil {
return nil, err
}
}
//创建watch
cancelCtx, cancelFun := context.WithCancel(context.Background())
//方法结束说明不需要再watch了
defer cancelFun()
watch := l.client.Watch(cancelCtx, genKey(key), clientv3.WithPrefix())
//首先尝试一波是否可以抢占,可以就返回lock
if lock, err := l.tryLock(key, id); err != nil {
return nil, err
} else if lock != nil {
return lock, nil
}
//select模式,如果超时了就不干了
for {
select {
case resp := <-watch:
for _, ev := range resp.Events {
if ev.Type == clientv3.EventTypeDelete {
if lock, err := l.tryLock(key, id); err != nil {
return nil, err
} else if lock != nil {
return lock, nil
}
}
}
case <-ctx.Done():
return nil, ctx.Err()
}
}
}
func genKey(key qtlock.Key) string {
return "/qt/lock/" + string(key)
}
运行结果
公平锁
package main
import (
"context"
"fmt"
"qtjob/qtlock"
_ "qtjob/qtlock-etcd"
"sync"
"time"
)
func main() {
wg := &sync.WaitGroup{}
wg.Add(10)
startTime := time.Now()
locker := qtlock.GetLocker(true)
for i := 0; i < 10; i++ {
time.Sleep(100 * time.Millisecond)
go func(i int) {
defer wg.Done()
if lock, err := locker.Lock(context.Background(), "my-key"); err != nil {
fmt.Println(i, "err", err, time.Now())
} else if lock != nil {
defer func() {
lock.Unlock()
fmt.Println(i, "释放锁", time.Now())
}()
fmt.Println(i, "获取锁", time.Now())
time.Sleep(1 * time.Second)
} else {
fmt.Println(i, "没有获取锁", time.Now().Sub(startTime).Seconds())
}
}(i)
}
wg.Wait()
fmt.Println("所有工作都完成啦", time.Now().Sub(startTime).Seconds())
}
以依次运行3个main
- 第一个
0 获取锁 2021-06-27 20:19:27.550466 +0800 CST m=+0.127847385
0 释放锁 2021-06-27 20:19:28.557069 +0800 CST m=+1.134474677
1 获取锁 2021-06-27 20:19:28.5642 +0800 CST m=+1.141605853
1 释放锁 2021-06-27 20:19:29.57077 +0800 CST m=+2.148199317
2 获取锁 2021-06-27 20:19:29.577877 +0800 CST m=+2.155306443
2 释放锁 2021-06-27 20:19:30.5882 +0800 CST m=+3.165653668
3 获取锁 2021-06-27 20:19:30.595688 +0800 CST m=+3.173141675
3 释放锁 2021-06-27 20:19:31.605569 +0800 CST m=+4.183047291
4 获取锁 2021-06-27 20:19:31.613068 +0800 CST m=+4.190545893
4 释放锁 2021-06-27 20:19:32.61865 +0800 CST m=+5.196151610
5 获取锁 2021-06-27 20:19:32.623703 +0800 CST m=+5.201205418
5 释放锁 2021-06-27 20:19:33.635073 +0800 CST m=+6.212598991
6 获取锁 2021-06-27 20:19:33.642475 +0800 CST m=+6.220001909
6 释放锁 2021-06-27 20:19:34.65735 +0800 CST m=+7.234900445
7 获取锁 2021-06-27 20:19:34.671907 +0800 CST m=+7.249458452
7 释放锁 2021-06-27 20:19:35.686387 +0800 CST m=+8.263961758
8 获取锁 2021-06-27 20:19:35.695422 +0800 CST m=+8.272997592
8 释放锁 2021-06-27 20:19:36.709496 +0800 CST m=+9.287095249
9 获取锁 2021-06-27 20:19:36.719634 +0800 CST m=+9.297233253
9 释放锁 2021-06-27 20:19:37.725698 +0800 CST m=+10.303321779
所有工作都完成啦 10.299433566
- 第二个
0 获取锁 2021-06-27 20:19:37.737731 +0800 CST m=+7.568569649
0 释放锁 2021-06-27 20:19:38.754212 +0800 CST m=+8.585075088
1 获取锁 2021-06-27 20:19:38.762839 +0800 CST m=+8.593702185
1 释放锁 2021-06-27 20:19:39.771861 +0800 CST m=+9.602748940
2 获取锁 2021-06-27 20:19:39.779315 +0800 CST m=+9.610202705
2 释放锁 2021-06-27 20:19:40.788968 +0800 CST m=+10.619879770
3 获取锁 2021-06-27 20:19:40.797028 +0800 CST m=+10.627939922
3 释放锁 2021-06-27 20:19:41.804775 +0800 CST m=+11.635711081
4 获取锁 2021-06-27 20:19:41.815311 +0800 CST m=+11.646246703
4 释放锁 2021-06-27 20:19:42.826904 +0800 CST m=+12.657865757
5 获取锁 2021-06-27 20:19:42.833435 +0800 CST m=+12.664394949
5 释放锁 2021-06-27 20:19:43.839824 +0800 CST m=+13.670808299
6 获取锁 2021-06-27 20:19:43.85154 +0800 CST m=+13.682524092
6 释放锁 2021-06-27 20:19:44.860329 +0800 CST m=+14.691337585
7 获取锁 2021-06-27 20:19:44.8655 +0800 CST m=+14.696508425
7 释放锁 2021-06-27 20:19:45.874083 +0800 CST m=+15.705116168
8 获取锁 2021-06-27 20:19:45.883744 +0800 CST m=+15.714776664
8 释放锁 2021-06-27 20:19:46.893219 +0800 CST m=+16.724276235
9 获取锁 2021-06-27 20:19:46.897397 +0800 CST m=+16.728454358
9 释放锁 2021-06-27 20:19:47.904127 +0800 CST m=+17.735208096
所有工作都完成啦 17.731764593
- 第三个
0 获取锁 2021-06-27 20:19:47.913892 +0800 CST m=+14.753784641
0 释放锁 2021-06-27 20:19:48.920926 +0800 CST m=+15.760842312
1 获取锁 2021-06-27 20:19:48.929371 +0800 CST m=+15.769287127
1 释放锁 2021-06-27 20:19:49.939623 +0800 CST m=+16.779564043
2 获取锁 2021-06-27 20:19:49.947469 +0800 CST m=+16.787409354
2 释放锁 2021-06-27 20:19:50.957638 +0800 CST m=+17.797602940
3 获取锁 2021-06-27 20:19:50.964732 +0800 CST m=+17.804697111
3 释放锁 2021-06-27 20:19:51.978776 +0800 CST m=+18.818765511
4 获取锁 2021-06-27 20:19:51.987461 +0800 CST m=+18.827450408
4 释放锁 2021-06-27 20:19:52.995747 +0800 CST m=+19.835760735
5 获取锁 2021-06-27 20:19:53.002322 +0800 CST m=+19.842335471
5 释放锁 2021-06-27 20:19:54.012092 +0800 CST m=+20.852129302
6 获取锁 2021-06-27 20:19:54.018343 +0800 CST m=+20.858380657
6 释放锁 2021-06-27 20:19:55.02701 +0800 CST m=+21.867072232
7 获取锁 2021-06-27 20:19:55.033416 +0800 CST m=+21.873477902
7 释放锁 2021-06-27 20:19:56.046365 +0800 CST m=+22.886451162
8 获取锁 2021-06-27 20:19:56.052991 +0800 CST m=+22.893077955
8 释放锁 2021-06-27 20:19:57.062726 +0800 CST m=+23.902836457
9 获取锁 2021-06-27 20:19:57.068247 +0800 CST m=+23.908357798
9 释放锁 2021-06-27 20:19:58.077912 +0800 CST m=+24.918046010
所有工作都完成啦 24.914203824
非公平锁
运行3个main
- 第一个
0 获取锁 2021-06-27 20:18:05.838694 +0800 CST m=+0.121275535
0 释放锁 2021-06-27 20:18:06.844003 +0800 CST m=+1.126608017
5 获取锁 2021-06-27 20:18:06.851627 +0800 CST m=+1.134232908
5 释放锁 2021-06-27 20:18:07.854577 +0800 CST m=+2.137206217
7 获取锁 2021-06-27 20:18:07.862737 +0800 CST m=+2.145366240
7 释放锁 2021-06-27 20:18:08.869402 +0800 CST m=+3.152055847
4 获取锁 2021-06-27 20:18:08.879202 +0800 CST m=+3.161856030
4 释放锁 2021-06-27 20:18:09.886098 +0800 CST m=+4.168775718
2 获取锁 2021-06-27 20:18:10.914925 +0800 CST m=+5.197627433
2 释放锁 2021-06-27 20:18:11.92025 +0800 CST m=+6.202976608
6 获取锁 2021-06-27 20:18:11.929309 +0800 CST m=+6.212035431
6 释放锁 2021-06-27 20:18:12.938716 +0800 CST m=+7.221467034
8 获取锁 2021-06-27 20:18:12.956149 +0800 CST m=+7.238900413
8 释放锁 2021-06-27 20:18:13.959678 +0800 CST m=+8.242453029
9 获取锁 2021-06-27 20:18:17.030777 +0800 CST m=+11.313624917
9 释放锁 2021-06-27 20:18:18.036743 +0800 CST m=+12.319615356
3 获取锁 2021-06-27 20:18:22.121173 +0800 CST m=+16.404142281
3 释放锁 2021-06-27 20:18:23.13125 +0800 CST m=+17.414243048
1 获取锁 2021-06-27 20:18:27.218464 +0800 CST m=+21.501554198
1 释放锁 2021-06-27 20:18:28.224302 +0800 CST m=+22.507416502
所有工作都完成啦 22.503525374
- 第二个
2 获取锁 2021-06-27 20:18:09.893969 +0800 CST m=+1.132471256
2 释放锁 2021-06-27 20:18:10.900968 +0800 CST m=+2.139494159
9 获取锁 2021-06-27 20:18:13.968598 +0800 CST m=+5.207197245
9 释放锁 2021-06-27 20:18:14.976034 +0800 CST m=+6.214657380
3 获取锁 2021-06-27 20:18:14.989242 +0800 CST m=+6.227865562
3 释放锁 2021-06-27 20:18:15.993106 +0800 CST m=+7.231752786
4 获取锁 2021-06-27 20:18:20.076725 +0800 CST m=+11.315469643
4 释放锁 2021-06-27 20:18:21.087402 +0800 CST m=+12.326171986
6 获取锁 2021-06-27 20:18:21.104958 +0800 CST m=+12.343726803
6 释放锁 2021-06-27 20:18:22.112438 +0800 CST m=+13.351231093
1 获取锁 2021-06-27 20:18:24.163994 +0800 CST m=+15.402835545
1 释放锁 2021-06-27 20:18:25.168774 +0800 CST m=+16.407639425
7 获取锁 2021-06-27 20:18:28.23352 +0800 CST m=+19.472458771
7 释放锁 2021-06-27 20:18:29.239593 +0800 CST m=+20.478557098
8 获取锁 2021-06-27 20:18:33.294351 +0800 CST m=+24.533410239
8 释放锁 2021-06-27 20:18:34.300123 +0800 CST m=+25.539205720
0 获取锁 2021-06-27 20:18:34.305126 +0800 CST m=+25.544209210
0 释放锁 2021-06-27 20:18:35.312746 +0800 CST m=+26.551853436
5 获取锁 2021-06-27 20:18:35.319791 +0800 CST m=+26.558898016
5 释放锁 2021-06-27 20:18:36.325765 +0800 CST m=+27.564896058
所有工作都完成啦 27.561429141
- 第三个
4 获取锁 2021-06-27 20:18:16.006294 +0800 CST m=+3.738873366
4 释放锁 2021-06-27 20:18:17.01444 +0800 CST m=+4.747043499
3 获取锁 2021-06-27 20:18:18.047145 +0800 CST m=+5.779772816
3 释放锁 2021-06-27 20:18:19.052704 +0800 CST m=+6.785355106
5 获取锁 2021-06-27 20:18:19.062197 +0800 CST m=+6.794849085
5 释放锁 2021-06-27 20:18:20.069998 +0800 CST m=+7.802673572
2 获取锁 2021-06-27 20:18:23.144169 +0800 CST m=+10.876918442
2 释放锁 2021-06-27 20:18:24.152375 +0800 CST m=+11.885148298
7 获取锁 2021-06-27 20:18:25.181316 +0800 CST m=+12.914113627
7 释放锁 2021-06-27 20:18:26.186676 +0800 CST m=+13.919497141
8 获取锁 2021-06-27 20:18:26.193912 +0800 CST m=+13.926733805
8 释放锁 2021-06-27 20:18:27.201253 +0800 CST m=+14.934098797
9 获取锁 2021-06-27 20:18:29.249341 +0800 CST m=+16.982235762
9 释放锁 2021-06-27 20:18:30.253136 +0800 CST m=+17.986054265
0 获取锁 2021-06-27 20:18:30.25859 +0800 CST m=+17.991508093
0 释放锁 2021-06-27 20:18:31.267492 +0800 CST m=+19.000434755
1 获取锁 2021-06-27 20:18:31.275035 +0800 CST m=+19.007977583
1 释放锁 2021-06-27 20:18:32.279909 +0800 CST m=+20.012875533
6 获取锁 2021-06-27 20:18:32.28645 +0800 CST m=+20.019416445
6 释放锁 2021-06-27 20:18:33.289617 +0800 CST m=+21.022607274
所有工作都完成啦 21.019210949
还有很多需要改进的地方
还有很多优化的地方
- key的命名规则
- watch机制
- 等等
等我学的更多吧~