最近在继续学习Go语言的过程中,发现了一个比较神奇的的对象sync.Once,顾名思义,就是执行一次。官方定义的如下:Once是一个只执行一个动作的对象,看包名sync知道这是在并发使用场
最近在继续学习Go语言的过程中,发现了一个比较神奇的的对象sync.Once,顾名思义,就是执行一次。官方定义的如下:Once是一个只执行一个动作的对象,看包名sync知道这是在并发使用场景。
基础使用方法如下:
// TestOnceSimple once对象简单测试 // @Description: // @param t // func TestOnceSimple(t *testing.T) { var once sync.Once for i := 0; i < 10; i++ { go once.Do(func() { log.Println("执行一次") }) } time.Sleep(time.Second) }for循环里面分别异步执行了10次,但是最终控制台展示如下:
=== RUN TestOnceSimple 2022/06/19 16:39:08 执行一次 --- PASS: TestOnceSimple (1.00s) PASS目前使用到场景中就是在各种配置进行初始化的时候,以防止多个异步同时来执行初始化任务导致异常。比如说,我再使用Redis连接池的时候,首先需要初始化连接池,通常需要一个方法来完成这个过程,大部分时候需要显式调用,除非这个池对于我们项目来讲是基础的功能,程序启动的时候就需要初始化。
这个在我写Java的过程中,用到的HTTP的连接池和MySQL的连接池,而后者就属于需要用的时候的再初始化的场景。还有一种方式,我们可以使用Java单例模式中的懒汉式的解决这个问题。但是我们如果在测试过程中使用不同的对象池的时候,这种方式又显得比较死板不够灵活。
所以在平时处理这种情况的时候,通常我会使用synchronized或者java.util.concurrent.locks.ReentrantLock等concurrent包里面的工具类完成这个需求。具体代码可参考Java单例的懒汉式的实现以及我之前的文章。
之前我对照Go语言的go异步关键字写了Java自定义异步功能实践,写了一个Java版本的fun异步关键字。这次我自然计划要抄一下sync.Once设计。
下面是我的经过自己尝试写了一个简版的:
static Vector<Integer> ones = new Vector<>(); static ReentrantLock lock = new ReentrantLock(); /** * 线程安全单次执行,仿照Go语言的once方法 * * @param v */ public static void once(Closure v) { try { lock.lock(); int code = v.hashCode(); if (!ones.contains(code)) { ones.add(code); v.call(); } } catch (Exception e) { logger.warn("once执行方法失败", e); } finally { lock.unlock(); } }下面是测试代码:
package com.okcoin.hickwall.presses import com.okcoin.hickwall.presses.funtester.httpclient.FunHttp class OnceTest extends FunHttp { public static void main(String[] args) { def test = { output("FunTester") } 10.times { fun { once(test) } } } }控制台输出:
16:56:22 main 守护线程:Deamon开启! 16:56:22 F-1 FunTester 16:56:23 Deamon 异步线程池等待执行1次耗时:6 ms 16:56:23 Deamon 异步线程池关闭!从上面内容我们看到,虽然异步执行了10次,但是只有一次真正执行了,实现了预期的需求。
Fun·BUG挖掘机·性能征服者·头顶锅盖·Tester