`

ReentrantLock原理的源码解读

 
阅读更多
可以参照http://blog.csdn.net/chen77716/article/details/6641477.本文的很多思想都来源于这个文章.这文章说的真的是很透.

我们主要关注lock和unlock方法


 public void lock() {
        sync.lock();
    }


直接调用的是sync的lock.


  public ReentrantLock() {
        sync = new NonfairSync();
    }


默认是非公平锁,看看NonfairSync的lock方法:

   final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }


compareAndSetState(0, 1) 这个是尝试获取锁,把state的状态从0改为1表示取得锁.这个时候设置获取锁的线程就是当前线程.
具体调用的是



 protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }


unsafe的compareAndSwapInt方法是native的.
但是我们更关注的是,申请锁不成功的时候是怎么做的.可以看到是acquire(1);


 public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }


acquire首先调用的是tryAcquire.看看NonfairSync的tryAcquire是怎么样实现的:

 protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }


final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }


tryAcquire的逻辑是这样的,
c = getState() 就是当前没有锁竞争的时候,会再尝试去获得锁.
current == getExclusiveOwnerThread()):当前线程已经获取锁了,那么锁的记数加一

如果tryAcquire没有成功,  就执行acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 
addWaiter是把线程和线程的状态信息封装到一个node对象,node是个链表.
其实就是把当前线程放到一个链表的末尾去.具体怎么放有点讲究,而且用到了无限循环,也就是说,一定要把线程放进链表的.


private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }


compareAndSetTail 这个方法就是尝试把当前线程放到一个链表的末尾去.
如果没有成功,执行enq(node);


private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                Node h = new Node(); // Dummy header
                h.next = node;
                node.prev = h;
                if (compareAndSetHead(h)) {
                    tail = node;
                    return h;
                }
            }
            else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }


for (;;)  这里用到了无限循环.
在addWaiter里,放进链表的条件是链表的结尾元素不能为null,在enq方法发现这种情况会创建一个node对象取代之前的链表的结尾元素.

现在来看acquireQueued方法:


final boolean acquireQueued(final Node node, int arg) {
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } catch (RuntimeException ex) {
            cancelAcquire(node);
            throw ex;
        }
    }

acquireQueued也是个无限循环.就是说要么获取到锁,要么中断当前线程.
acquireQueued会再次调用tryAcquire,就是再尝试一次获取锁.
shouldParkAfterFailedAcquire是判断是否要中断当前线程.


private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {
	    do {
		node.prev = pred = pred.prev;
	    } while (pred.waitStatus > 0);
	    pred.next = node;
        } else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        } 
        return false;
    }


shouldParkAfterFailedAcquire返回true的话,当前线程就直接中断了
返回false的话,会无限循环再来一次,期间会删除掉废弃的node(pred.waitStatus > 0)
会一直尝试把前面的节点的waitStatus设置为SIGNAL,这个其实就是返回true的条件.
也就是说会不断尝试直到返回true,然后中断当前线程.

线程这个时候还没获取锁,但是已经被中断了,这个时候只有等待被唤醒,然后再尝试去或得锁.
这个逻辑是可以在unlock中看到的:


public void unlock() {
        sync.release(1);
    }

 public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }


protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            if (c == 0) {
                free = true;
                setExclusiveOwnerThread(null);
            }
            setState(c);
            return free;
        }

从tryRelease可以看到释放锁的条件是:c == 0 就是锁的计数为0;
unparkSuccessor:释放锁.


 private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0); 

        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }


LockSupport.unpark(s.thread);  可以看到释放锁的时候会唤醒一个之前链表的一个线程,这样线程的加锁和解锁就串起来了.  这里最终调用的是unsafe.unpark.

线程等待最终调用的是unsafe.park.
线程唤醒最终调用的是unsafe.park.
可以看到unsafe才是最终的实现,也可以看到unsafe的方法都是native的.








0
2
分享到:
评论

相关推荐

    基于.net+SQLserver旅游网站平台源码-说明文档资料.zip

    旅游网站首页主要功能有:首页、景点信息、客房信息、美食信息、用户分享、公告信息、个人中心、后台管理、帮助中心等。

    基于MATLAB的储能电站建模与评价:平抑可再生能源功率波动,优化风光曲线与负荷匹配度,储能电站平抑可再生能源功率波动的建模与评价:策略设计与并网优化分析,用于平抑可再生能源功率波动的储能电站建模及评

    基于MATLAB的储能电站建模与评价:平抑可再生能源功率波动,优化风光曲线与负荷匹配度,储能电站平抑可再生能源功率波动的建模与评价:策略设计与并网优化分析,用于平抑可再生能源功率波动的储能电站建模及评价 关键词:储能电站 功率波动 并网 平抑可再生能源 参考文档:《用于平抑可再生能源功率波动的储能电站建模及评价》仅参考 《光伏发电容量可信度评估》参考风电与负荷一致性问题思路 仿真平台:MATLAB yalmip 主要内容:代码主要做的是一个通过储能电站平抑可再生能源波动的问题,通过储能电站平抑可再生能源的波动,建立了两种不同的储能平抑策略,使得风电功率曲线以及光伏曲线变得光滑,从而可以减少并网功率波动;此外,还研究了如何通过储能电站使得风光曲线与负荷曲线趋于一致,从而更好的将分布式能源用于供负荷。 实现效果良好,具体看图。 ,关键词:储能电站; 功率波动; 并网; 平抑可再生能源; 建模; 评价; 风光曲线; 负荷曲线。,基于MATLAB yalmip的储能电站建模与评价:平抑可再生能源功率波动的优化策略

    基于matlab实现的水下图像增强融合算法+源码(毕业设计&课程设计&项目开发)

    基于matlab实现的水下图像增强融合算法+源码,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于matlab实现的水下图像增强融合算法+源码,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用 基于matlab实现的水下图像增强融合算法+源码,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用 基于matlab实现的水下图像增强融合算法+源码,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于matlab实现的水下图像增强融合算法+源码,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~ 基于matlab实现的水下图像增强融合算法+源码,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用~

    基于java开发的安卓多窗口管理界面,仿UC浏览器多窗口管理界面+源码+文档(毕业设计&课程设计&项目开发)

    基于java开发的安卓多窗口管理界面,仿UC浏览器多窗口管理界面+源码+文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档~ 基于java开发的安卓多窗口管理界面,仿UC浏览器多窗口管理界面+源码+文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 基于java开发的安卓多窗口管理界面,仿UC浏览器多窗口管理界面+源码+文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档 基于java开发的安卓多窗口管理界面,仿UC浏览器多窗口管理界面+源码+文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档~ 基于java开发的安卓多窗口管理界面,仿UC浏览器多窗口管理界面+源码+文档,适合毕业设计、课程设计、项目开发。项目源码已经过严格测试,可以放心参考并在此基础上延申使用,详情见md文档~

    exceptionLogs.zip

    exceptionLogs.zip

    34页-智慧方案之海尔智慧社区解决方案.pdf

    在当今科技日新月异的时代,智慧社区的概念正悄然改变着我们的生活方式。它不仅仅是一个居住的空间,更是一个集成了先进科技、便捷服务与人文关怀的综合性生态系统。以下是对智慧社区整体解决方案的精炼融合,旨在展现其知识性、趣味性与吸引力。 一、智慧社区的科技魅力 智慧社区以智能化设备为核心,通过综合运用物联网、大数据、云计算等技术,实现了社区管理的智能化与高效化。门禁系统采用面部识别技术,让居民无需手动操作即可轻松进出;停车管理智能化,不仅提高了停车效率,还大大减少了找车位的烦恼。同时,安防报警系统能够实时监测家中安全状况,一旦有异常情况,立即联动物业进行处理。此外,智能家居系统更是将便捷性发挥到了极致,通过手机APP即可远程控制家中的灯光、窗帘、空调等设备,让居民随时随地享受舒适生活。 视频监控与可视对讲系统的结合,不仅提升了社区的安全系数,还让居民能够实时查看家中情况,与访客进行视频通话,大大增强了居住的安心感。而电子巡更、公共广播等系统的运用,则进一步保障了社区的治安稳定与信息传递的及时性。这些智能化设备的集成运用,不仅提高了社区的管理效率,更让居民感受到了科技带来的便捷与舒适。 二、智慧社区的增值服务与人文关怀 智慧社区不仅仅关注科技的运用,更注重为居民提供多元化的增值服务与人文关怀。社区内设有互动LED像素灯、顶层花园控制喷泉等创意设施,不仅美化了社区环境,还增强了居民的归属感与幸福感。同时,社区还提供了智能家居的可选追加项,如空气净化器、远程监控摄像机等,让居民能够根据自己的需求进行个性化选择。 智慧社区还充分利用大数据技术,对居民的行为数据进行收集与分析,为居民提供精准化的营销服务。无论是周边的商业信息推送,还是个性化的生活建议,都能让居民感受到社区的智慧与贴心。此外,社区还注重培养居民的环保意识与节能意识,通过智能照明、智能温控等系统的运用,鼓励居民节约资源、保护环境。 三、智慧社区的未来发展与无限可能 智慧社区的未来发展充满了无限可能。随着技术的不断进步与创新,智慧社区将朝着更加智能化、融合化的方向发展。比如,利用人工智能技术进行社区管理与服务,将能够进一步提升社区的智能化水平;而5G、物联网等新技术的运用,则将让智慧社区的连接更加紧密、服务更加高效。 同时,智慧社区还将更加注重居民的体验与需求,通过不断优化智能化设备的功能与服务,让居民享受到更加便捷、舒适的生活。未来,智慧社区将成为人们追求高品质生活的重要选择之一,它不仅是一个居住的空间,更是一个融合了科技、服务、人文关怀的综合性生态系统,让人们的生活更加美好、更加精彩。 综上所述,智慧社区整体解决方案以其科技魅力、增值服务与人文关怀以及未来发展潜力,正吸引着越来越多的关注与认可。它不仅能够提升社区的管理效率与居民的生活品质,更能够为社区的可持续发展注入新的活力与动力。

    云上考场系统 2024免费JAVA微信小程序毕设

    2024免费微信小程序毕业设计成品,包括源码+数据库+往届论文资料,附带启动教程和安装包。 启动教程:https://www.bilibili.com/video/BV1BfB2YYEnS 讲解视频:https://www.bilibili.com/video/BV1BVKMeZEYr 技术栈:Uniapp+Vue.js+SpringBoot+MySQL。 开发工具:Idea+VSCode+微信开发者工具。

    回测模型示例(非实盘交易策略) #HS300日线下运行,20个交易日进行 一次调仓,每次买入在买入备选中因子评分前10的股票,每支股票各分配当前可用资金的10%(权重可调整) #扩展数据需要在补完HS

    回测模型示例(非实盘交易策略) #HS300日线下运行,20个交易日进行 一次调仓,每次买入在买入备选中因子评分前10的股票,每支股票各分配当前可用资金的10%(权重可调整) #扩展数据需要在补完HS300成分股数据之后生成,本模型中扩展数据暂时使用VBA指标ATR和ADTM生成,命名为atr和adtm

    Matlab Simulink 下的 Buck和Boost型双向DC-DC变换器:电压电流双闭环PI控制,恒功率负载,优质波形,2020b版本专业搭建,MATLAB Simulink下的Buck与Bo

    Matlab Simulink 下的 Buck和Boost型双向DC-DC变换器:电压电流双闭环PI控制,恒功率负载,优质波形,2020b版本专业搭建,MATLAB Simulink下的Buck与Boost型双向DC-DC变换器:电压电流双闭环PI控制,恒功率负载,优质波形,20b版本全新搭建,matlab simulink:buck型降压双向dc dc变器和boost型升压双向dc dc变器,均采用电压电流双闭环PI控制,负载为恒功率负载,波形质量良好,可自行调试参数 版本matlab2020b,所有部分均由simulink模块搭建,由于部分模块低版本没有,因此只能用20b或以上版本 ,核心关键词: 1. Buck型降压双向DC-DC变换器 2. Boost型升压双向DC-DC变换器 3. 电压电流双闭环PI控制 4. 恒功率负载 5. 波形质量 6. Matlab 2020b 7. Simulink模块搭建 8. 模块版本要求,Matlab Simulink下的双向DC-DC变换器设计与参数调试研究

    购物系统 2024免费JAVA微信小程序毕设

    2024免费微信小程序毕业设计成品,包括源码+数据库+往届论文资料,附带启动教程和安装包。 启动教程:https://www.bilibili.com/video/BV1BfB2YYEnS 讲解视频:https://www.bilibili.com/video/BV1BVKMeZEYr 技术栈:Uniapp+Vue.js+SpringBoot+MySQL。 开发工具:Idea+VSCode+微信开发者工具。

    COMSOL环境下应力作用下瓦斯渗透运移模型的建立及研究-流固耦合物理场的分析与PDE+结构力学模块应用,应力作用下COMSOL岩层开挖瓦斯渗透运移模型:流固耦合物理场考虑与PDE+结构力学模块应用

    COMSOL环境下应力作用下瓦斯渗透运移模型的建立及研究——流固耦合物理场的分析与PDE+结构力学模块应用,应力作用下COMSOL岩层开挖瓦斯渗透运移模型:流固耦合物理场考虑与PDE+结构力学模块应用研究,comsol岩层开挖作用下瓦斯渗透运移模型,考虑应力作用下的渗透率变化,流固耦合物理场,使用pde+结构力学模块,参考相关文献建立。 ,核心关键词:COMSOL;岩层开挖;瓦斯渗透运移模型;应力作用;渗透率变化;流固耦合物理场;PDE+结构力学模块;参考文献。,应力作用下瓦斯渗透运移模型研究

    DeepSeek Coder 2开源编码模型.pdf

    deepseek最新资讯、配置方法、使用技巧,持续更新中

    电动滑板车电池建模研究:基于MATLAB Simulink的电池与电容模块、BMS模块及仿真参数可视化分析,电动滑板车电池的MATLAB Simulink建模与仿真:涵盖电池与电容模块、BMS管理及参

    电动滑板车电池建模研究:基于MATLAB Simulink的电池与电容模块、BMS模块及仿真参数可视化分析,电动滑板车电池的MATLAB Simulink建模与仿真:涵盖电池与电容模块、BMS管理及参数可视化,电动滑板车的电池建模 MATLAB Simulink模型 包括电池与电容模块、电池管理系统BMS模块、仿真参数可视化等 ,电动滑板车电池建模; MATLAB Simulink模型; 电池与电容模块; BMS模块; 仿真参数可视化,基于MATLAB的电动滑板车电池建模与仿真

    永磁同步电机模型预测转矩控制(MPTC)的最优电压矢量实时计算研究参考 参考文章:作者未给出具体的参考文献 可能涉及的领域包括电机控制理论、电力电子技术和自动控制原理等 ,永磁同步电机的模型预测转

    永磁同步电机模型预测转矩控制(MPTC)的最优电压矢量实时计算研究参考 参考文章:作者未给出具体的参考文献。可能涉及的领域包括电机控制理论、电力电子技术和自动控制原理等。,永磁同步电机的模型预测转矩控制MPTC:优化电压矢量选择与直接转矩控制的对比研究参考文献:[具体文献名],永磁同步电机模型预测转矩控制MPTC MPTC采用实时在线计算的方式确保预施加的电压矢量为最优电压矢量,与直接转矩控制相比,该方法选取的电压矢量更为合理有效。 提供对应的参考文献; ,关键词:永磁同步电机模型;预测转矩控制(MPTC);实时在线计算;最优电压矢量;直接转矩控制。,永磁同步电机MPTC模型预测转矩控制及其电压矢量优化研究

    xorg-x11-fonts-ISO8859-1-100dpi-7.5-9.el7.x64-86.rpm.tar.gz

    1、文件内容:xorg-x11-fonts-ISO8859-1-100dpi-7.5-9.el7.rpm以及相关依赖 2、文件形式:tar.gz压缩包 3、安装指令: #Step1、解压 tar -zxvf /mnt/data/output/xorg-x11-fonts-ISO8859-1-100dpi-7.5-9.el7.tar.gz #Step2、进入解压后的目录,执行安装 sudo rpm -ivh *.rpm 4、更多资源/技术支持:公众号禅静编程坊

    基于浣熊优化算法与多通信半径跳距加权策略的改进Dvhop定位算法研究及性能对比分析,MATLAB: 基于浣熊优化算法与多参数加权策略改进的Dvhop定位算法对比研究报告,matlab:基于浣熊优化算法

    基于浣熊优化算法与多通信半径跳距加权策略的改进Dvhop定位算法研究及性能对比分析,MATLAB: 基于浣熊优化算法与多参数加权策略改进的Dvhop定位算法对比研究报告,matlab:基于浣熊优化算法的多通信半径和跳距加权的改进Dvhop定位算法 - 将浣熊优化算法COA用于Dvhop定位估计,并加入了多通信半径和跳距加权策略 - 将原始Dvhop、COA-Dvhop以及本资源算法进行对比 - 对比1:在不同锚节点比例下,对三种算法进行归一化定位误差对比 - 对比2:在不同通信半径下,对三种算法进行归一化定位误差对比 - 对比3:在不同总节点数下,对三种算法进行归一化定位误差对比 - 注释详细 ,基于浣熊优化算法; 多通信半径; 跳距加权; Dvhop定位算法; 归一化定位误差对比。,基于COA的改进Dvhop定位算法:多通信半径与跳距加权优化

    ML.net检测诈骗短信测试项目

    文件内包含训练数据,以及训练好的模型,和模型调用方法

    基于模糊逻辑的并联式混合动力车辆控制策略(含WLTC、NEDC工况仿真效果):发动机与电机转矩变化、档位与电池SOC动态调整,百公里燃油消耗与速度跟随性能优化 ,基于模糊逻辑的并联式混合动力车辆控制策

    基于模糊逻辑的并联式混合动力车辆控制策略(含WLTC、NEDC工况仿真效果):发动机与电机转矩变化、档位与电池SOC动态调整,百公里燃油消耗与速度跟随性能优化。,基于模糊逻辑的并联式混合动力车辆控制策略:工况下的发动机与电机转矩分配及整车性能仿真研究,基于模糊逻辑的并联式混合动力车辆控制策略 ①(工况可自行添加)已有WLTC、NEDC工况; ②仿真图像包括 发动机转矩变化图像、电机转矩变化图像、档位变化图像、电池SOC变化图像、等效百公里燃油消耗量图像、速度跟随图像、车速变化图像; ③整车similink模型中包含工况输入模型、发动机模型、电机模型、制动能量回收模型、转矩分配模型、档位切模型纵向动力学模型. 仿真效果良好 ,基于模糊逻辑的混合动力控制策略;WLTC、NEDC工况;仿真图像;转矩变化图像;档位变化图像;电池SOC变化;等效百公里燃油消耗;速度跟随;车速变化;整车simulink模型;工况输入模型;发动机模型;电机模型;制动能量回收模型;转矩分配模型;档位切换模型纵向动力学模型。,基于模糊逻辑的混合动力车辆控制策略:多模型协同仿真与工况优化

    自适应蚁群优化算法AACO:融合六大改进策略,栅格路径规划的新突破,改进自适应蚁群优化算法:融合多重改进策略应用于栅格路径规划,改进蚁群优化算法(AACO,自己改进的蚁群优化算法) 改进策略包括如下

    自适应蚁群优化算法AACO:融合六大改进策略,栅格路径规划的新突破,改进自适应蚁群优化算法:融合多重改进策略应用于栅格路径规划,改进蚁群优化算法(AACO,自己改进的蚁群优化算法) 改进策略包括如下,主要自适应思想 1.自适应信息素重要度因子,自适应阿尔法 2.自适应启发式因子,自适应贝塔 3.改进启发函数 4.信息素奖惩机制 5.自适应挥发系数 6.3次B样条曲线平滑路径 融合这6个改进后的蚁群算法,被应用在解决栅格路径规划,效果如下。 拿后AACO后,ACO、EACO(精英蚁)和AS(序列蚁群)程序都有。 看完AACO代码后,您会对ACO算法有更加清晰的认识,以及如何改进,有利于二次开发优化。 再次优化之处(曲线平滑后路径是更短的,并没有输出,其次曲线会有一定波动,并没有在最优解处收敛) 智能优化算法不可避免每次运行结果不同,多次运行,取最优值即可。 ,AACO; 自适应思想; 改进策略; 自适应信息素重要度因子; 自适应阿尔法; 自适应启发式因子; 改进启发函数; 信息素奖惩机制; 自适应挥发系数; 3次B样条曲线平滑路径; 栅格路径规划; ACO算法; EACO; AS序列蚁群

    DeepSeek R1 与 V3:两种 AI 模型的正面比较.pdf

    deepseek最新资讯、配置方法、使用技巧,持续更新中

Global site tag (gtag.js) - Google Analytics