<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
    <id>https://www.sunjianbo.com</id>
    <title>孙建博的小站</title>
    <updated>2021-02-18T07:52:46.385Z</updated>
    <generator>https://github.com/jpmonette/feed</generator>
    <link rel="alternate" href="https://www.sunjianbo.com"/>
    <link rel="self" href="https://www.sunjianbo.com/atom.xml"/>
    <subtitle>互联网、编程、生活感悟，在这里希望你我都能有所收获，得到提升</subtitle>
    <logo>https://www.sunjianbo.com/images/avatar.png</logo>
    <icon>https://www.sunjianbo.com/favicon.ico</icon>
    <rights>All rights reserved 2021, 孙建博的小站</rights>
    <entry>
        <title type="html"><![CDATA[为什么HashMap链表存储结构转换为红黑树的长度临界点是8?]]></title>
        <id>https://www.sunjianbo.com/why-threshold-is-8/</id>
        <link href="https://www.sunjianbo.com/why-threshold-is-8/">
        </link>
        <updated>2021-02-18T07:30:10.000Z</updated>
        <content type="html"><![CDATA[<p>为什么HashMap链表存储结构转换为红黑树的长度临界点是8?</p>
<ol>
<li>
<p>分布规律</p>
<p>概率统计学,在随机哈希代码下,链表长度超过8的概率非常非常小</p>
<p>​	0: 0.60653066<br>
​	1: 0.30326533<br>
​	2: 0.07581633<br>
​	3: 0.01263606<br>
​	4: 0.00157952<br>
​	5: 0.00015795<br>
​	6: 0.00001316<br>
​	7: 0.00000094<br>
​	8: 0.00000006<br>
​	more: less than 1 in ten million</p>
</li>
<li>
<p>时间复杂度</p>
<p>首先只有红黑树的效率高过链表才有转换的必要</p>
<p>红黑树的平均查找长度是log(n)，长度为8，查找长度为log(8)=3</p>
<p>链表的平均查找长度为n/2，当长度为6时，查找长度为6/2=3 这时和红黑树效率持平</p>
<p>当长度一致为8时，平均查找长度为8/2=4</p>
<p>2.1 为什么不是6?</p>
<p>转化为树结构是有成本的</p>
<p>2.2 为什么不是7?</p>
<p>如果超过7则转换为数,低于7则转换为链表,就有可能导致频繁的转换,转换是有成本的</p>
<p>所以中间加了个缓冲,转换的临界点设定为了8和6</p>
</li>
</ol>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[一些东西从腾讯云迁到群晖docker]]></title>
        <id>https://www.sunjianbo.com/qcloud-move-to-nas/</id>
        <link href="https://www.sunjianbo.com/qcloud-move-to-nas/">
        </link>
        <updated>2021-01-15T08:49:06.000Z</updated>
        <content type="html"><![CDATA[<p>腾讯云服务器即将到期,所以迁移一些东西,主要是leanote以及宝塔面板上面一些也不知道还用不用的网站服务</p>
<h2 id="1群晖docker上下载好镜像">1.群晖docker上下载好镜像</h2>
<h3 id="11-群晖docker镜像下载加速">1.1 群晖docker镜像下载加速</h3>
<p>打开docker-注册表-设置-选择dockerhub编辑-启用注册表镜像,填入url:</p>
<pre><code>https://mirror.ccs.tencentyun.com
</code></pre>
<figure data-type="image" tabindex="1"><img src="https://static.sunjianbo.com/20210115150646.png" alt="mirror" loading="lazy"></figure>
<h3 id="12-提前下载好相关镜像">1.2 提前下载好相关镜像</h3>
<p>因为文件挺大的,下载要很长时间,所以我开始下载之后第二天才进行后续的步骤</p>
<p>下载镜像如下:</p>
<blockquote>
<p>axboy/leanote:latest				//2.6.1带数据库版leanote</p>
<p>pch18/baota:clear				 //宝塔面板lastest有5个多G,clear不默认安装nginx,mysql,php等程序</p>
</blockquote>
<h2 id="2docker配置">2.docker配置</h2>
<h3 id="21-leanote">2.1 leanote</h3>
<h4 id="211-leanote-docker配置">2.1.1 leanote docker配置</h4>
<p>选中<code>axboy/leanote:latest</code>镜像,点击启动</p>
<h5 id="卷设置">卷设置</h5>
<p>名称随便配,点击高级设置,切换到卷标签,进行目录的一些映射,官方推荐映射以下内容</p>
<pre><code>/data/db                # 内置mongodb的数据目录，nodb版无此目录
/data/leanote/conf      # 笔记的配置文件目录
/data/leanote/files     # 笔记内上传的图片、文件存放目录
/data/leanote/public/upload     # 头像上传路径
</code></pre>
<p>我是这样映射的</p>
<figure data-type="image" tabindex="2"><img src="https://static.sunjianbo.com/image-20210111155030511.png" alt="image-20210111155030511" loading="lazy"></figure>
<h5 id="端口配置">端口配置</h5>
<p>考虑不会有其他应用使用这个mongodb,所以端口删除<code>27017</code>,只保留<code>9000</code>的映射</p>
<figure data-type="image" tabindex="3"><img src="https://static.sunjianbo.com/image-20210112091044152.png" alt="image-20210112091044152" loading="lazy"></figure>
<p>其他按需修改,也可以默认</p>
<p>最后应用</p>
<h4 id="212-leanote-数据迁移">2.1.2 leanote 数据迁移</h4>
<h5 id="数据库迁移">数据库迁移</h5>
<p>浏览器用管理账号登录源站，在后台备份数据库，下载到本地</p>
<figure data-type="image" tabindex="4"><img src="https://static.sunjianbo.com/image-20210115094529792.png" alt="image-20210115094529792" loading="lazy"></figure>
<p>下载好文件上传到群晖中</p>
<p>ssh登陆群晖,输入以下命令(/volume1/docker/newleanote/files/是我上传backup_leanote_1610674560.tar.gz到的群晖的目录)</p>
<pre><code># 将备份压缩包cp到容器中的/tmp 
sudo docker cp /volume1/docker/newleanote/files/backup_leanote_1610674560.tar.gz axboy-leanote-new:/tmp/
</code></pre>
<p>然后进入docker</p>
<pre><code>sudo docker exec -it axboy-leanote-new bash
# 解压缩
mkdir /tmp/leanotedb
tar -zxvf /tmp/backup_leanote_1610674560.tar.gz -C /tmp/leanotedb/
# 恢复备份的数据库
mongorestore -h localhost -d leanote --dir /tmp/leanotedb/  --drop
</code></pre>
<figure data-type="image" tabindex="5"><img src="https://static.sunjianbo.com/image-20210115110422651.png" alt="image-20210115110422651" loading="lazy"></figure>
<p>0 failed 数据库就导入成功了</p>
<h5 id="数据迁移">数据迁移</h5>
<p>备份源站leanote目录下的files目录,放到docker映射的群晖目录(/volume1/docker/newleanote/files/)即可</p>
<h5 id="其他">其他</h5>
<ol>
<li>登陆新站admin账户,在后台设置中重新设置对应的mongo命令地址</li>
</ol>
<pre><code>/usr/bin/mongodump
/usr/bin/mongorestore
</code></pre>
<p>​	在新站备份一下数据库试试</p>
<ol start="2">
<li>登陆新站admin账户,在后台设置中修改相应的<code>Site's URL</code>以正确的展示图片</li>
</ol>
<blockquote>
<p>leanote 初始密码:admin/abc123</p>
<p>这个其实用不到,我只是记录一下</p>
</blockquote>
<h3 id="22-宝塔面板">2.2 宝塔面板</h3>
<h4 id="221宝塔面板-docker配置">2.2.1宝塔面板 docker配置</h4>
<h5 id="卷配置">卷配置</h5>
<pre><code>/www/server/	# 面板文件
/www/backup/	# 备份目录
/www/wwwroot/	# 宝塔面板的网站根目录文件夹路径
/www/wwwlogs	# 站点日志
</code></pre>
<p>但是我们选的这个镜像只支持backup和wwwroot目录的映射</p>
<figure data-type="image" tabindex="6"><img src="https://static.sunjianbo.com/image-20210115145039965.png" alt="image-20210115145039965" loading="lazy"></figure>
<h5 id="端口配置-2">端口配置</h5>
<p>端口只映射管理端口<code>8888</code>好了</p>
<figure data-type="image" tabindex="7"><img src="https://static.sunjianbo.com/image-20210112090846685.png" alt="image-20210112090846685" loading="lazy"></figure>
<p>其他按需修改,也可以默认</p>
<p>应用并启动</p>
<h5 id="无法登陆问题">无法登陆问题</h5>
<p>使用docker创建者提供的初始的用户名密码<code>username/password</code>登陆错误,通过以下方法解决</p>
<pre><code>sudo docker exec -it 运行的容器ID bash
bt
5
输入新的密码
</code></pre>
<p>然后使用<code>username/新密码</code>登陆</p>
<h4 id="222-宝塔面板数据迁移">2.2.2 宝塔面板数据迁移</h4>
<p>以前使用过宝塔的一键迁移插件,体验非常好,本来以为这次应该很容易,结果遇到了两个问题</p>
<ol>
<li>5版本到6版本宝塔进行了底层架构层面的修改,导致无法直接迁移,甚至能下载的迁移插件都是两个东西了</li>
<li>那么考虑同样使用5版本的docker镜像部署之后,迁移时报错</li>
</ol>
<figure data-type="image" tabindex="8"><img src="https://static.sunjianbo.com/image-20210113092119777.png" alt="image-20210113092119777" loading="lazy"></figure>
<p>​	安全组里都是开启的,我考虑映射一下22端口到本机,但是总是提示占用,修改了群晖内置的ssh端口也依旧是占用</p>
<p>那么就决定先把迁出端的宝塔升级到7再试一下吧</p>
<p>然而杯具的是我的系统是centos6.5,不能升级到7版本o(≧口≦)o</p>
<p>最后放弃一键迁移的打算,采用了手动在新宝塔上添加网站,然后覆盖相关文件的方案,具体很简单不细说了</p>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[AQS抽象队列同步器详解]]></title>
        <id>https://www.sunjianbo.com/aqs/</id>
        <link href="https://www.sunjianbo.com/aqs/">
        </link>
        <updated>2020-09-04T07:24:54.000Z</updated>
        <content type="html"><![CDATA[<h2 id="1-观察一下锁的基本功能">1. 观察一下锁的基本功能</h2>
<p>同步锁的本质 - 排队</p>
<p><code>同步的方式:</code>独享锁-单个队列窗口,共享锁-多个队列窗口</p>
<p><code>抢锁的方式:</code>插队抢(不公平锁)、先来后到抢锁(公平锁)</p>
<p><code>没抢到锁的处理方式:</code>快速尝试多次(CAS自旋锁)、阻塞等待</p>
<p><code>唤酲阻塞线程的方式(叫号器):</code>全部通知、通知下一个</p>
<h2 id="2-先手撸一个简单的独占锁">2. 先手撸一个简单的独占锁</h2>
<pre><code class="language-java">// 自己实现(独享锁) - 常用的
public class MyLock implements Lock {
     // 如何判断一个锁的状态,或者说 
    volatile  AtomicReference&lt;Thread&gt; owner = new AtomicReference&lt;&gt;();
    // 保存正在等待的线程
    volatile LinkedBlockingQueue&lt;Thread&gt; waiters = new LinkedBlockingQueue&lt;&gt;();

    @Override
    public boolean tryLock() {
        return owner.compareAndSet(null, Thread.currentThread());
    }

    @Override
    public void lock() {
        boolean addQ = true;//防止重复加入集合,这里用了最简单的方式,演示,多少有点问题,领会精神就行
        while (!tryLock()) {
            if (addQ) {
                // 没拿到锁,加入到等待集合
                waiters.offer(Thread.currentThread());
                addQ = false;
            } else {
                // 阻塞 挂起当前线程
                LockSupport.park();//伪唤醒,就是非unpark唤醒的,所以上面用while
                // 后续，等待其他线程释放锁，收到通知之后继续循环
            }
        }
        waiters.remove(Thread.currentThread());
    }

    @Override
    public void unlock() {
        // cas 修改 owner 拥有者
        if (owner.compareAndSet(Thread.currentThread(), null)) {
            Iterator&lt;Thread&gt; iterator = waiters.iterator();
            while (iterator.hasNext()) {
                Thread waiter = iterator.next();
                LockSupport.unpark(waiter); // 唤醒线程继续 抢锁
            }

        }
    }
    ...
</code></pre>
<p>嗯,写的很不错,查看一下java源码看看是不是差不多</p>
<pre><code class="language-java">public class ReentrantLock {
    private final Sync sync;

    abstract static class Sync extends AbstractQueuedSynchronizer {
        ...
    }

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

    public boolean tryLock() {
        return sync.nonfairTryAcquire(1);
    }

    public void unlock() {
        sync.release(1);
    }
</code></pre>
<p>嗯?源码怎么这么简洁,<code>sync</code>是什么鬼?</p>
<p><code>Sync extends AbstractQueuedSynchronizer</code></p>
<p>这就是<strong>AQS</strong></p>
<h2 id="3-aqs抽象队列同步器的概念">3. AQS抽象队列同步器的概念</h2>
<p>提供了对资源占用、释放,线程的等待、唤醒等等接口的定义和具体实现</p>
<p>可以用在各种需要控制资源争用的场景中。(ReentrantLock/ CountDownLatch/ Semphore)</p>
<figure data-type="image" tabindex="1"><img src="https://static.sunjianbo.com/image-20200831160051565.png" alt="image-20200831160051565" loading="lazy"></figure>
<p><code>acquire、 acquireShared:</code>定义了资源争用的逻辑,如果没拿到,则等待。</p>
<p><code>tryAcquire、 tryAcquireShared:</code>实际执行占用资源的操作,内容由使用者具体去实现。</p>
<p><code>release、 releaseShared:</code>定义释放资源的逻辑,释放之后,通知后续节点进行争抢。</p>
<p><code>tryRelease、 tryReleaseShared:</code>实际执行资源释放的操作,具体内容由AQS使用者去实现。</p>
<h2 id="4-我们也自己整一个aqs">4. 我们也自己整一个AQS</h2>
<pre><code class="language-java">// 抽象队列同步器 - 还是独占的
// state， owner， waiters
public class MyAqs {
    // 1、 如何判断一个资源的拥有者
    public volatile AtomicReference&lt;Thread&gt; owner = new AtomicReference&lt;&gt;();
    // 保存 正在等待的线程
    public volatile LinkedBlockingQueue&lt;Thread&gt; waiters = new LinkedBlockingQueue&lt;&gt;();
    
    public boolean tryAcquire() { // 交给使用者去实现。 模板方法设计模式
        throw new UnsupportedOperationException();
    }

    public void acquire() {
        boolean addQ = true;
        while (!tryAcquire()) {
            if (addQ) {
                // 没拿到锁，加入到等待集合
                waiters.offer(Thread.currentThread());
                addQ = false;
            } else {
                // 阻塞 挂起当前的线程，不要继续往下跑了
                LockSupport.park(); // 伪唤醒，就是非unpark唤醒的
            }
        }
        waiters.remove(Thread.currentThread()); // 把线程移除
    }

    public boolean tryRelease() {
        throw new UnsupportedOperationException();
    }

    public void release() { // 定义了 释放资源之后要做的操作
        if (tryRelease()) {
            // 通知等待者
            Iterator&lt;Thread&gt; iterator = waiters.iterator();
            while (iterator.hasNext()) {
                Thread next = iterator.next();
                LockSupport.unpark(next); // 唤醒
            }
        }
    }
}
</code></pre>
<p>然后在MyLock中使用AQS</p>
<pre><code class="language-java">// 自己实现(独享锁) - 常用的
public class MyLock implements Lock {
	// 抽象工具类AQS
    MyAqs aqs = new MyAqs(){
        @Override
        public boolean tryAcquire() {
            return owner.compareAndSet(null, Thread.currentThread());
        }

        @Override
        public boolean tryRelease() {
            // 可重入的情况下，要判断资源的占用情况（state字段保存了资源的占用次数）
            return owner.compareAndSet(Thread.currentThread(), null);
        }
    };

    @Override
    public boolean tryLock() {
        return aqs.tryAcquire();
    }

    @Override
    public void lock() {
        aqs.acquire();
    }

    @Override
    public void unlock() {
        aqs.release();
    }
    ...
</code></pre>
<p>这样就和ReentrantLock源码差不多了,源码大体思路就是这样,只是源码AQS考虑了更多适用的情况,添加了更多的功能</p>
<p>比如公平锁和非公平锁的实现,公平锁是lock()直接进去资源争抢阶段acquire();非公平锁是lock()进来先尝试直接占用资源,然后才进入acquire()争抢</p>
<p>而且等待队列不是用的Queue而是用的链表,其中也使用了很多CAS,有兴趣的可以完整看一遍.</p>
<p>补充一份资源占用流程</p>
<figure data-type="image" tabindex="2"><img src="https://static.sunjianbo.com/image-20200901144318257.png" alt="image-20200901144318257" loading="lazy"></figure>
<h2 id="5-aqs的其他应用-信号量-倒计数器和回环栅栏">5. AQS的其他应用 - 信号量、倒计数器和回环栅栏</h2>
<p>AQS是一种很优雅的抽象,她还可以用来实现更多的操作</p>
<p>再说应用之前我们还要补充一下AQS中共享的资源占用的一些方法</p>
<h3 id="51-完善一下我们的aqs">5.1 完善一下我们的AQS</h3>
<p>这里增加一些共享资源争用的方法,其实就是前面提到的<code>acquireShared,tryAcquireShared,releaseShared,tryReleaseShared</code></p>
<p><code>release、 releaseShared:</code>定义释放资源的逻辑,释放之后,通知后续节点进行争抢。</p>
<p><code>tryRelease、 tryReleaseShared:</code>实际执行资源释放的操作,具体内容由AQS使用者去实现</p>
<pre><code class="language-java"> 	// 记录资源状态
    public volatile AtomicInteger state = new AtomicInteger(0);
    public AtomicInteger getState() { return state; }
    public void setState(AtomicInteger state) { this.state = state; }

	// 共享资源占用的逻辑，返回资源的占用情况
    public int tryAcquireShared(){
        throw new UnsupportedOperationException();
    }

    public void acquireShared(){
        boolean addQ = true;
        while(tryAcquireShared() &lt; 0) {
            if (addQ) {
                // 没拿到锁，加入到等待集合
                waiters.offer(Thread.currentThread());
                addQ = false;
            } else {
                // 阻塞 挂起当前的线程，不要继续往下跑了
                LockSupport.park(); // 伪唤醒，就是非unpark唤醒的
            }
        }
        waiters.remove(Thread.currentThread()); // 把线程移除
    }

    public boolean tryReleaseShared(){
        throw new UnsupportedOperationException();
    }

    public void releaseShared(){
        if (tryReleaseShared()) {
            // 通知等待者
            Iterator&lt;Thread&gt; iterator = waiters.iterator();
            while (iterator.hasNext()) {
                Thread next = iterator.next();
                LockSupport.unpark(next); // 唤醒
            }
        }
    }
 	// 独占资源相关的代码
	...
</code></pre>
<h3 id="52-semaphore">5.2 Semaphore</h3>
<p>又称&quot;信号量&quot;,控制多个线程争抢许可。</p>
<ul>
<li><code>acquire:</code>获取一个许可,如果没有就等待</li>
<li><code>release:</code>释放一个许可。</li>
<li><code>availablePermits:</code>方法得到可用的许可数目</li>
</ul>
<p><strong>使用场景示例</strong></p>
<ol>
<li>代码并发处理限流(hystrix)</li>
</ol>
<h4 id="照例手撸一个mysemaphore">照例手撸一个MySemaphore</h4>
<pre><code class="language-java">// 自定义的信号量实现
public class MySemaphore {
    MyAqs aqs = new MyAqs() {
        @Override
        public int tryAcquireShared() { // 信号量获取， 数量 - 1
            for(;;) {
                int count =  getState().get();
                int n = count - 1;
                if(count &lt;= 0 || n &lt; 0) {
                    return -1;
                }
                if(getState().compareAndSet(count, n)) {
                    return 1;
                }
            }
        }

        @Override
        public boolean tryReleaseShared() { // state + 1
            return getState().incrementAndGet() &gt;= 0;
        }
    };

    /** 许可数量 */
    public MySemaphore(int count) {
        aqs.getState().set(count); // 设置资源的状态
    }

    public void acquire() {
        aqs.acquireShared();
    } // 获取令牌

    public void release() {
        aqs.releaseShared();
    } // 释放令牌
}

</code></pre>
<h3 id="53-countdownlatch">5.3 CountDownLatch</h3>
<p>java1.5被引入的一个工具类,常被称为:倒计数器。</p>
<p>创建对象时,传入指定数值作为线程参与的数量;</p>
<p><code>await</code>:方法等待计数器值变为0,在这之前,线程进入等待状态;</p>
<p><code>countdown</code>:计数器数值减一,直到为0;</p>
<p>经常用于等待其他线程执行到某一节点,再继续执行当前线程代码</p>
<p><strong>使用场景示例</strong></p>
<ol>
<li>统计线程执行的情况</li>
<li>压力测试中,使用 countDownLatch实现最大程度的并发处理;</li>
<li>多个线程之间,相互通信,比如线程异步调用完接口,结果通知;</li>
</ol>
<blockquote>
<p>类似田径运动,八个赛道运动员依次准备,等到所有人就绪,裁判才吹响口哨</p>
</blockquote>
<h4 id="依旧手撸一个mycountdownlatch">依旧手撸一个MyCountDownLatch</h4>
<pre><code class="language-java">// CountDownLatch 自己实现
public class  MyCountDownLatch {
    MyAqs myAqs = new MyAqs() {
        @Override
        public int tryAcquireShared() { // 如果非等于0，代表当前还有线程没准备就绪，则认为需要等待
            return this.getState().get() == 0 ? 1 : -1;
        }

        @Override
        public boolean tryReleaseShared() { // 如果非等于0，代表当前还有线程没准备就绪，则不会通知继续执行
            return this.getState().decrementAndGet() == 0;
        }
    };

    public  MyCountDownLatch(int count) {
        myAqs.setState(new AtomicInteger(count));
    }

    public void await() {
        myAqs.acquireShared();
    }

    public void countDown() {
        myAqs.releaseShared();
    }
}
</code></pre>
<h3 id="54-cyclicbarrier">5.4 CyclicBarrier</h3>
<p>也是1.5加入的,又称为&quot;线程栅栏&quot;,&quot;回环栅栏&quot;。</p>
<p>创建对象时,指定栅栏线程数量。</p>
<p><code>await</code>:等指定数量的线程都处于等待状态时,继续执行后续代码。</p>
<p><code>barrierAction</code>:线程数量到了指定量之后,自动触发执行指定任务。</p>
<p>栅栏没有显式的使用AQS,她使用锁,每一个线程获取一把锁,且count--,当count为0时,signalAll()且重置count</p>
<p><strong>使用场景示例</strong></p>
<ol>
<li>数据量比较大时,实现批量插入数据到数据库;</li>
<li>数据统计,30个线程统计30天数据,全部统计完毕后,执行汇总</li>
</ol>
<blockquote>
<p>类似打游戏,比如吃鸡,每凑够100人就开始一场游戏</p>
</blockquote>
<h4 id="撸秃噜皮了的mycyclicbarrier">撸秃噜皮了的MyCyclicBarrier</h4>
<pre><code class="language-java">public class MyCyclicBarrier {
    private int count;//计数
    private int initNum;//用于重置count
    private Runnable barrierAction;//barrierAction
    private ReentrantLock lock = new ReentrantLock();
    private Condition trip = lock.newCondition();

    public MyCyclicBarrier(int initNum, Runnable barrierAction){
        this.initNum = initNum;
        this.count = initNum;
        this.barrierAction = barrierAction;
    }

    public void await() throws InterruptedException {
        lock.lock();
        try {
            if (--count == 0) {  // tripped 数量够了
                if (barrierAction != null)
                    barrierAction.run(); // 触发执行指定的任务
                // 唤醒等待的线程继续执行。重新计数
                trip.signalAll(); // 唤醒线程
                count = initNum; // count重置
                return;
            }
            //没到数量,进入等待
            trip.await();
        } finally {
            lock.unlock();
        }
    }
}
</code></pre>
<h3 id="55-countdownlatch和cyclicbarrier的区别">5.5 CountDownLatch和CyclicBarrier的区别</h3>
<ol>
<li>
<p>CountDownLatch只是一次计数, CyclicBarrier对象可多次触发执行;</p>
</li>
<li>
<p>CountDownLatch的多个参与者只是参与计数,不会阻塞本身,后面的代码继续执行,只有等待计数归零的代码在阻塞;</p>
<p>而CyclicBarrier的多个参与者在执行到await()方法的时候都会被栅栏拦住,直到满足数量的参与者就绪才会开门放行</p>
<p>所以CountDownLatch相当于一个人在等其他人就绪后做什么,而CyclicBarrier是所有人等其他人就绪后一起开始做什么</p>
</li>
<li>
<p>同时,CountDownLatch需要已知总数,当总数不固定的时候没法使用</p>
</li>
</ol>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[简七理财001-小白理财第一课]]></title>
        <id>https://www.sunjianbo.com/jianqilicai001/</id>
        <link href="https://www.sunjianbo.com/jianqilicai001/">
        </link>
        <updated>2020-08-31T07:19:30.000Z</updated>
        <content type="html"><![CDATA[<p><strong>书名:</strong>	简七理财001-小白理财第一课</p>
<p><strong>作者:</strong>	简七理财</p>
<p><strong>阅读时间:</strong>	20200826~20200827</p>
<hr>
<h3 id="读后感">读后感:</h3>
<p>拿到掌阅f1第一天,随便找了本看起来顺眼的书开始打卡</p>
<p>断断续续累积两个小时读完,这个时间用的有点长了,以后这种书还是要读的更快一点</p>
<p>书看起来是13年的,很多东西过时了,甚至里面还推荐大家买P2P理财【瑟瑟发抖】</p>
<p>整个看下来发现最有用的还是自我投资这一部分,其他的看过就忘就好,对我来说没什么价值</p>
<h3 id="笔记">笔记:</h3>
<p>理财不是让我们不花钱，而是让我们成为金钱的主人，通过规划去更好的更安心的生活</p>
<p>一个人能够取得的最终成就，不是由你的起点决定的，而是在过程中是否能有清晰的目标、保持规划并且严格的执行。所以你的生活会变得怎么样，完全取决于你愿不愿意从现在开始去规划自己的未来。</p>
<p>投资自己四部曲</p>
<ol>
<li>
<p>投资自己的视野和洞察力</p>
<p>方式一是阅读，大量的阅读。方式二是旅行，勇敢滴旅行。</p>
</li>
<li>
<p>投资自己挑战的勇气和技巧</p>
<p>如何定义“挑战”？手到擒来那不算，要就要踮起脚尖来都够不到，必须跳起来才有机会触到的才是真正的挑战。</p>
<p>☆尝试一次练摊儿：比如在圣诞节晚上去商业街兜售小饰品，并与城管斗智斗勇； ☆尝试一个人旅行：从做攻略到完成旅行全程靠自己完成，如要增加难度可考虑出境游；</p>
<p>☆尝试在100人面前演讲：主动申请一个机会，在一群人面前演讲一个你感兴趣的题目； ☆坚持一种运动的习惯：例如坚持晨跑、或者坚持游泳三个月。</p>
</li>
<li>
<p>投资自己独特的眼光和方法论</p>
<p>除了阅读、学习、体验之外，还要会总结和提炼</p>
</li>
<li>
<p>投资自己的心态和情绪管理</p>
</li>
</ol>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[JAVA锁相关]]></title>
        <id>https://www.sunjianbo.com/synchronized/</id>
        <link href="https://www.sunjianbo.com/synchronized/">
        </link>
        <updated>2020-08-28T08:26:10.000Z</updated>
        <content type="html"><![CDATA[<h1 id="java中锁的概念">JAVA中锁的概念</h1>
<ul>
<li>
<p><strong>自旋锁</strong>:为了不放弃CPU执行事件,循环的使用CAS技术对数据尝试进行更新,直至成功。</p>
</li>
<li>
<p><strong>悲观锁</strong>:假定会发生并发冲突,同步所有对数据的相关操作,从读数据就开始上锁。</p>
</li>
<li>
<p><strong>乐观锁</strong>:假定没有冲突,在修改数据时如果发现数据和之前获取的不一致,则读最新数据,修改后重试修改。</p>
</li>
<li>
<p><strong>独享锁(写)</strong>:给资源加上写锁,线程可以修改资源,其他线程不能再加锁;(单写)</p>
</li>
<li>
<p><strong>共享锁(读)</strong>:给资源加上读锁后只能读不能改,其他线程也只能加读锁,不能加写锁;(多读)</p>
</li>
</ul>
<figure data-type="image" tabindex="1"><img src="https://static.sunjianbo.com/image-20200827093337312.png" alt="image-20200827093337312" loading="lazy"></figure>
<ul>
<li><strong>可重入锁、不可重入锁</strong>:线程拿到一把锁之后,可以自由进入同一把锁所同步的其他代码。</li>
</ul>
<figure data-type="image" tabindex="2"><img src="https://static.sunjianbo.com/image-20200827093401077.png" alt="image-20200827093401077" loading="lazy"></figure>
<ul>
<li><strong>公平锁、非公平锁</strong>:争抢锁的顺序,如果是按先来后到,则为公平。</li>
</ul>
<p>几种重要的锁实现方式: synchronized、 ReentrantLock、 ReentrantReadWriteLock</p>
<h1 id="同步关键字-synchronized">同步关键字 synchronized</h1>
<h2 id="概念">概念</h2>
<p>属于最基本的线程通信机制,基于对象监视器实现的。</p>
<p>Java中的每个对象都与一个监视器相关联,一个线程可以锁定或解锁。</p>
<p>一次只有一个线程可以锁定监视器。</p>
<p>试图锁定该监视器的任何其他线程都会被阻塞,直到它们可以获得该监视器上的锁定为止。</p>
<h3 id="持性">持性:</h3>
<ul>
<li>可重入</li>
<li>独享</li>
<li>悲观锁</li>
</ul>
<h3 id="锁的范围">锁的范围:</h3>
<ul>
<li>类锁</li>
<li>对象锁</li>
<li>锁消除</li>
<li>锁粗化</li>
</ul>
<h3 id="可见性">可见性</h3>
<p>同步关鍵字,不仅是实现同步,根据JMM规定还能保证可见性(读取最新主内存数据,结束后写入主内存)</p>
<h2 id="代码示例">代码示例</h2>
<h3 id="synchronized代码示例">synchronized代码示例</h3>
<pre><code class="language-java">// 锁 方法(静态/非静态),代码块(对象/类)
public class ObjectSyncDemo1 {

    //普通方法+同步关键字,不能同步,因为锁的是对象,main里两个线程创建了两个对象
    public synchronized void test1(){
		try {
			System.out.println(Thread.currentThread() + &quot; 我开始执行&quot;);
			Thread.sleep(3000L);
			System.out.println(Thread.currentThread() + &quot; 我执行结束&quot;);
		} catch (InterruptedException e) {
		}
    }
    
    //static方法+同步关键字,能同步,因为锁的是类
    public synchronized static void test1(){
		try {
			System.out.println(Thread.currentThread() + &quot; 我开始执行&quot;);
			Thread.sleep(3000L);
			System.out.println(Thread.currentThread() + &quot; 我执行结束&quot;);
		} catch (InterruptedException e) {
		}
    }
        
    //synchronized (this),不能同步
    static Object temp = new Object();
    public void test1() {
		synchronized (this) {
            try {
                System.out.println(Thread.currentThread() + &quot; 我开始执行&quot;);
                Thread.sleep(3000L);
                System.out.println(Thread.currentThread() + &quot; 我执行结束&quot;);
            } catch (InterruptedException e) {
            }
        }
    }
    
    //synchronized(static变量),synchronized(ObjectSyncDemo1.class),能同步,两个线程争抢的是同一把锁
    static Object temp = new Object();
    public void test1() {
		//synchronized (ObjectSyncDemo1.class) {
        synchronized (temp) {
            try {
                System.out.println(Thread.currentThread() + &quot; 我开始执行&quot;);
                Thread.sleep(3000L);
                System.out.println(Thread.currentThread() + &quot; 我执行结束&quot;);
            } catch (InterruptedException e) {
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        new Thread(() -&gt; {
            new ObjectSyncDemo1().test1();
        }).start();

        Thread.sleep(1000L); // 等1秒钟,让前一个线程启动起来
        new Thread(() -&gt; {
            new ObjectSyncDemo1().test1();
        }).start();
    }
}
</code></pre>
<h3 id="可重入的代码示例">可重入的代码示例</h3>
<pre><code class="language-java">// 可重入
public class ObjectSyncDemo2 {

    public synchronized void test1(Object arg) {
        System.out.println(Thread.currentThread() + &quot; 我开始执行 &quot; + arg);
        if (arg == null) {
            test1(new Object());
        }
        System.out.println(Thread.currentThread() + &quot; 我执行结束&quot; + arg);
    }

    public static void main(String[] args) throws InterruptedException {
        new ObjectSyncDemo2().test1(null);
    }
}

</code></pre>
<h3 id="锁粗化的概念">锁粗化的概念</h3>
<p>https://www.oracle.com/java/technologies/javase/6performance.html#2.1.2</p>
<p>锁粗化是发生在编译器级别(JIT)的一种锁优化方式。</p>
<p>JVM会对热点代码(不间断、高频地)中的多个锁合并成一个锁,以降低短时间内大量锁请求、同步、释放带来的性能损耗。</p>
<p>注意：这样做是有前提的，就是中间不需要同步的代码能够很快速地完成，如果不需要同步的代码需要花很长时间，就会导致同步块的执行需要花费很长的时间，这样做也就不合理了。</p>
<pre><code class="language-java">// 锁粗化(运行时 jit 编译优化)
// jit 编译后的汇编内容, jitwatch可视化工具进行查看,修改优化级 别为C2
public class ObjectSyncDemo3 {
    int i;

    public void test1(Object arg) {
        synchronized (this) {
            i++;
        }
        synchronized (this) {
            i++;
        }
        //jvm会对热点代码进行优化,变成下面这样
        /*
        synchronized (this) {
            i++;
            i++;
        }*/
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i &lt; 10000000; i++) {
            new ObjectSyncDemo3().test1(&quot;a&quot;);
        }
    }
}

</code></pre>
<h3 id="锁消除的概念">锁消除的概念</h3>
<p>锁消除是发生在编译器级别(JIT)的一种锁优化方式。</p>
<p>有时候我们写的代码完全不需要加锁，却执行了加锁操作。</p>
<p>比如StringBuffer类的append操作,方法内部是加锁的,所以在执行一系列的append操作时JVM就会在保证线程安全的情况下消除锁</p>
<pre><code class="language-java">// 锁消除(jit)
public class ObjectSyncDemo4 {
    public void test3(Object arg) {
        StringBuilder builder = new StringBuilder();
        builder.append(&quot;a&quot;);
        builder.append(arg);
        builder.append(&quot;c&quot;);
        System.out.println(arg.toString());
    }

    public void test2(Object arg) {
        String a = &quot;a&quot;;
        String c = &quot;c&quot;;
        System.out.println(a + arg + c);
    }


    public void test1(Object arg) {
        // jit 优化, 消除了锁
        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append(&quot;a&quot;);
        stringBuffer.append(arg);
        stringBuffer.append(&quot;c&quot;);
        // System.out.println(stringBuffer.toString());
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i &lt; 1000000; i++) {
            new ObjectSyncDemo4().test1(&quot;123&quot;);
        }
    }
}
</code></pre>
<h2 id="同步关键字加锁原理">同步关键字加锁原理</h2>
<figure data-type="image" tabindex="3"><img src="https://static.sunjianbo.com/image-20200827140333983.png" alt="image-20200827140333983" loading="lazy"></figure>
<p>HotSpot中,对象前面会有一个类指针和标题,储标识哈希码的标题字以及用于分代垃圾收集的年龄和标记位</p>
<blockquote>
<p>参考来源:</p>
<p>https://www.cs.princeton.edu/picasso/mats/HotspotOverview.pdf</p>
<p>https://wiki.openjdk.java.net/display/HotSpot/Synchronization</p>
</blockquote>
<h3 id="cas轻量级加锁其实就是替换mark-word-的内容">CAS轻量级加锁其实就是替换mark word 的内容</h3>
<figure data-type="image" tabindex="4"><img src="https://static.sunjianbo.com/image-20200827155650779.png" alt="image-20200827155650779" loading="lazy"></figure>
<figure data-type="image" tabindex="5"><img src="https://static.sunjianbo.com/image-20200827155756686.png" alt="image-20200827155756686" loading="lazy"></figure>
<p>使用CAS修改mark word完毕,加锁成功。则mark word中的tag进入<code>00</code>状态(01=unlocked,00=Light-Weight locked,10=Heavy-weight locked)。</p>
<p>解锁的过程,则是一个逆向恢复 mark word的过程</p>
<h3 id="偏向锁到轻量级锁">偏向锁到轻量级锁</h3>
<p>默认情况下JVM锁会经历:偏向锁--&gt;轻量级锁--&gt;重量级锁这四个状态</p>
<figure data-type="image" tabindex="6"><img src="https://static.sunjianbo.com/image-20200827153255997.png" alt="image-20200827153255997" loading="lazy"></figure>
<p>偏向标记第一次有用,出现过争用后就没用了。<code>-XX: -UseBiasedLocking</code>禁用使用偏置锁定,<code>-XX:+UseBiasedLocking</code>设置启用偏向锁。</p>
<p>偏向锁,本质就是无锁,如果没有发生过任何多线程争抢锁的情况,JVM认为就是单线程,无需做同步</p>
<blockquote>
<p>JVM为了少千活:同步在JVM底层是有很多操作来实现的,如果是没有争用,就不需要去做同步操作</p>
</blockquote>
<figure data-type="image" tabindex="7"><img src="https://static.sunjianbo.com/image-20200827160155278.png" alt="image-20200827160155278" loading="lazy"></figure>
<h3 id="重量级锁-监视器monitor">重量级锁 - 监视器(monitor)</h3>
<p>修改mark word如果失败,会自旋CAS一定次数,该次数可以通过参数配置</p>
<p>超过次数,仍未抢到锁,则锁升级为重量级锁进入阻塞。</p>
<p>monitor也叫做管程,计算机操作系统原理中有提及类似概念。一个对象会有一个对应的monitor。</p>
<figure data-type="image" tabindex="8"><img src="https://static.sunjianbo.com/image-20200827161120818.png" alt="image-20200827161120818" loading="lazy"></figure>
<p>在虚拟机的源码中可以看到监视器的内容</p>
<h1 id="简单过一下lock锁相关">简单过一下Lock锁相关</h1>
<table>
<thead>
<tr>
<th>方法</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>lock</td>
<td>获取锁的方法,若锁被其他线程获取,则等待(阻塞)</td>
</tr>
<tr>
<td>lockInterruptibly</td>
<td>在锁的获取过程中可以中断当前线程</td>
</tr>
<tr>
<td>tryLock</td>
<td>尝试非阻塞地获取锁,立即返回</td>
</tr>
<tr>
<td>unlock</td>
<td>释放锁</td>
</tr>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
<blockquote>
<p>根据Lock接口的源码注释,Lock接口的实现,具备和同步关键字同样的内存语义。</p>
</blockquote>
<h2 id="reentrantlock">ReentrantLock</h2>
<p>独享锁;支持公平锁、非公平锁两种模式;可重入锁;</p>
<p><code>ReentrantLock</code>在上锁时，会根据实例化时指定的策略去获取锁，默认为非公平锁。如果上锁成功，锁状态值+1(重入，最大次数为 <code>Integer.MAX_VALUE</code>)，并将锁持有者设置为当前线程实例。在 <code>Sync(AQS)</code> 内部维护了一个队列，存放了所有上锁失败的线程。公平锁在上锁前，会检查在自己前面是否还有其他线程等待，如果有就放弃竞争，继续等待。而非公平锁会抓住每个机会，不管是否前面是否还有其它线程等待，只顾上锁</p>
<p><code>ReetrantLock</code>在释放锁时，将状态计数器减一(重入)，当状态计数器为0时，锁可用。此时再从等待队列中寻找合适的线程唤醒，默认从队首开始，如果队列正在更新中，且未找到合适的线程，那么从队尾开始寻找。</p>
<h2 id="readwritelock">ReadWriteLock</h2>
<p>维护一对关联锁,一个用于只读操作,一个用于写入;读锁可以由多个读线程同时持有,写锁是排他的。</p>
<p>适合读取线程比写入线程多的场景,改进互斥锁的性能,</p>
<p>示例场景:缓存组件、集合的并发线程安全性改造。</p>
<p>写锁是线程独占,读锁是共享,所以写-&gt;读是升级。(读-&gt;写,是不能实现的)</p>
<h3 id="关于锁降级">关于锁降级</h3>
<p><code>锁降级</code>指的是写锁降级成为读锁。把持住当前拥有的写锁的同时,再获取到读锁,随后释放写锁的过程。</p>
<pre><code class="language-java">import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

// 缓存示例
public class CacheDataDemo {
    // 创建一个map用于缓存
    private Map&lt;String, Object&gt; map = new HashMap&lt;&gt;();
    private static ReadWriteLock rwl = new ReentrantReadWriteLock();

    public static void main(String[] args) {
        // 1 读取缓存里面的数据
        // cache.query()
        // 2 如果缓存没数据,则取数据库里面查询  database.query()
        // 3 查询完成之后,数据塞到塞到缓存里面 cache.put(data)
    }

    public Object get(String id) {
        Object value = null;
        // 首先开启读锁，从缓存中去取
        rwl.readLock().lock();
        try {
            if (map.get(id) == null) {
                // TODO database.query();  全部查询数据库 ,缓存雪崩
                // 必须释放读锁
                rwl.readLock().unlock();
                // 如果缓存中没有释放读锁，上写锁。如果不加锁，所有请求全部去查询数据库，就崩溃了
                rwl.writeLock().lock(); // 所有线程在此处等待  1000  1  999 (在同步代码里面再次检查是否缓存)
                try {
                    // 双重检查，防止已经有线程改变了当前的值，从而出现重复处理的情况
                    if (map.get(id) == null) {
                        // TODO value = ...如果缓存没有，就去数据库里面读取
                    }
                    rwl.readLock().lock(); // 加读锁降级写锁,这样就不会有其他线程能够改这个值，保证了数据一致性			
                } finally {
                    rwl.writeLock().unlock(); // 释放写锁@
                }
            }
            //TODO cache.query();锁降级就是为了在这里保持数据可见性,避免在前面释放写锁后数据被别的线程修改
            
        } finally {
            rwl.readLock().unlock();
        }
        return value;
    }
}
</code></pre>
<p>如果当前的线程<em>C</em>在修改完cache中的数据后，没有获取读锁而是直接释放了写锁，那么假设此时另一个线程<em>T</em>获取了写锁并修改了数据，那么<em>C</em>线程无法感知到数据已被修改，则数据出现错误。</p>
<p>如果遵循锁降级的步骤，线程<em>C</em>在释放写锁之前获取读锁，那么线程<em>T</em>在获取写锁时将被阻塞，直到线程<em>C</em>完成数据处理过程，释放读锁。</p>
<h2 id="condition">Condition</h2>
<p>用于替代wait/notify</p>
<p>Object中的 wait(), notify(), notifyAll()方法是和 synchronized配合使用的,可以唤醒一个或者全部(单个等待集)</p>
<p>Condition是需要与Lock配合使用的,<strong>提供多个等待集合,更精确的控制</strong>(底层是park/ unpark机制);</p>
<figure data-type="image" tabindex="9"><img src="https://static.sunjianbo.com/image-20200828160438102.png" alt="image-20200828160438102" loading="lazy"></figure>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[线程安全以及原子操作]]></title>
        <id>https://www.sunjianbo.com/thread-safe-atomic/</id>
        <link href="https://www.sunjianbo.com/thread-safe-atomic/">
        </link>
        <updated>2020-08-25T07:45:53.000Z</updated>
        <content type="html"><![CDATA[<h2 id="线程安全概念">线程安全概念</h2>
<h3 id="竞态条件与临界区">竞态条件与临界区</h3>
<pre><code class="language-java">public class Demo{
    public int i=0;
    public void incr(){
        i++
    }
}
</code></pre>
<p>多个线程访问了相同的资源,向这些资源做了<strong>写操作</strong>时,对执行顺序有要求</p>
<p>临界区:incr方法內部就是临界区域,关键部分代码的多线程并发执行,会对执行结果产生影响。</p>
<p>竞态条件:可能发生在临界区域内的特殊条件。多线程执行incr方法中的i++关键代码时,就产生了竞态条件。</p>
<h2 id="一些情况是线程安全的">一些情况是线程安全的</h2>
<h3 id="资源不共享">资源不共享</h3>
<p>如果一段代码是线程安全的,则它不包含竞态条件。只有当多个线程更新共享资源时,才会发生竞态条件。</p>
<p>栈封闭时,不会在线程之间共享的变量,都是线程安全的。</p>
<p>局部对象引用本身不共享,但是引用的对象存储在共享堆中。如果方法內创建的对象,只是在方法中传递,并且不对其他线程可用,那么也是线程安全的。</p>
<pre><code class="language-java">//例子
public void someMethod(){
    LocalObject localObject = new LocalObject();
    localObject.callMethod();
    method2(localObject);
}
public void method2(LocalObject localObject{
	localObject.setValue(&quot;value&quot;);
}
</code></pre>
<blockquote>
<p>判定规则:如果创建、使用和处理资源,永远不会逃脱单个线程的控制,该资源的使用是线程安全的。</p>
</blockquote>
<h3 id="对象不可变">对象不可变</h3>
<p>创建不可变的共享对象来保证对象在线程间共享时不会被修改,从而实现线程安全。</p>
<p>实例被创建,value变量就不能再被修改,这就是不可变性。</p>
<pre><code class="language-java">//比如没有提供set方法的私有属性
public class Demo{
    private int value =0;
    public Demo(int value){
        this.value= value
    }
    public int getValue(){
        return this.value;
    }
}
</code></pre>
<h3 id="原子操作">原子操作</h3>
<h4 id="原子操作定义">原子操作定义</h4>
<p>原子操作可以是一个步骤,也可以是多个操作步骤,但是其顺序不可以被打乱,也不可以被切割而只执行其中的一部分(不可中断性)。</p>
<p><font color=red>将整个操作视作一个整体是原子性的核心特征。</font></p>
<p>最开始例子里面的i++就不是一个原子操作,她包含三步骤:</p>
<ol>
<li>加载i</li>
<li>计算+1</li>
<li>赋值i</li>
</ol>
<p>存在竞态条件的情况下,线程不安全,需要转变为原子操作才能安全。</p>
<h4 id="方式一-循环cas">方式一: 循环CAS</h4>
<h5 id="cas机制">CAS机制</h5>
<p><code>Compare and swap</code>比较和交换。属于硬件同步原语,处理器提供了基本内存操作的原子性保证。</p>
<p>CAS操作需要输入两个数值,一个旧值A(期望操作前的值)和一个新值B,在操作期间先比较下旧值有没有发生变化,如果没有发生变化,才交换成新值,发生了变化则不交换。(类似乐观锁)</p>
<p>JAVA中的 <code>sun.misc.Unsafe</code>类,提供了 <code>compareAndSwaplnt()</code>和 <code>compareAndSwapLong()</code>等几个方法实现CAS</p>
<pre><code class="language-java">package com.study.lock.lock;

import sun.misc.Unsafe;
import java.lang.reflect.Field;

public class LockDemo1 {
    volatile int value = 0;

    static Unsafe unsafe; // 直接操作内存，修改对象，数组内存....强大的API
    private static long valueOffset;

    static {
        try {
            // 反射技术获取unsafe值
            Field field = Unsafe.class.getDeclaredField(&quot;theUnsafe&quot;);
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);

            // 获取到 value 属性偏移量（用于定位value属性在内存中的具体地址）
            valueOffset = unsafe.objectFieldOffset(LockDemo1.class
                    .getDeclaredField(&quot;value&quot;));

        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public void add() {
        // TODO xx00
        // i++;// JAVA 层面三个步骤
        // CAS + 循环 重试
        int current;
        do {
            // 操作耗时的话， 那么 线程就会占用大量的CPU执行时间
            current = unsafe.getIntVolatile(this, valueOffset);
        } while (!unsafe.compareAndSwapInt(this, valueOffset, current, current + 1));
        // 因为跨平台的问题,而我们是用反射的非正常方式获得unsafe,所以可能会失败
    }

    public static void main(String[] args) throws InterruptedException {
        LockDemo1 ld = new LockDemo1();

        for (int i = 0; i &lt; 2; i++) {
            new Thread(() -&gt; {
                for (int j = 0; j &lt; 10000; j++) {
                    ld.add();
                }
            }).start();
        }
        Thread.sleep(2000L);
        System.out.println(ld.value);
    }
}
</code></pre>
<blockquote>
<p>其实AtomicInteger这些封装类的底层就是这么写的</p>
</blockquote>
<h5 id="javautilconcurrent包内的原子操作封装类">java.util.concurrent包内的原子操作封装类</h5>
<ul>
<li>
<p>AtomicBoolean:原子更新布尔类型</p>
</li>
<li>
<p>AtomicInteger:原子更新整型</p>
</li>
<li>
<p>AtomicLong:原子更新长整型</p>
</li>
<li>
<p>AtomicIntegerArray:原子更新整型数组里的元素</p>
</li>
<li>
<p>AtomicLongArray:原子更新长整型数组里的元素</p>
</li>
<li>
<p>AtomicReferenceArray:原子更新引用类型数组里的元素</p>
</li>
<li>
<p>AtomiclntegerFieldUpdater:原子更新整型的字段的更新器</p>
</li>
<li>
<p>AtomicLongFieldUpdater:原子更新长整型字段的更新器</p>
</li>
<li>
<p>AtomicReferenceFieldUpdater:原子更新引用类型里的字段</p>
</li>
<li>
<p>AtomicReference:原子更新引用类型</p>
</li>
<li>
<p>AtomicStampedReference:原子更新带有版本号的引用类型</p>
</li>
<li>
<p>AtomicMarkableReference:原子更新带有标记位的引用类型。</p>
<p>jdk1.8更新</p>
</li>
<li>
<p>更新器: DoubleAccumulator、 LongAccumulator</p>
</li>
<li>
<p>计数器: DoubleAdder、 LongAdder(一个使用场景是统计接口调用次数时可使用)</p>
</li>
</ul>
<blockquote>
<p>计数器增强版,高并发下性能更好</p>
<p>频繁更新但不太频繁读取的汇总统计信息时使用</p>
<p>分成多个操作单元,不同线程更新不同的单元只有需要汇总的时候才计算所有单元的操作</p>
</blockquote>
<blockquote>
<p>即分成多个值(Cell)减少竞争,对外暴露一个虚拟值,最后取得时候使用sum()把多个内存块的Cell相加</p>
<figure data-type="image" tabindex="1"><img src="https://static.sunjianbo.com/image-20200825135731552.png" alt="image-20200825135731552" loading="lazy"></figure>
</blockquote>
<h5 id="三个缺点">三个缺点:</h5>
<ol>
<li>循环+CAS,自旋的实现让所有线程都处于高频运行,争抢CPU执行时间的状态。如果操作长时间不成功,会带来很大的cPU资源消耗。</li>
<li>仅针对单个变量的操作,不能用于多个变量来实现原子操作。即很长的代码没法用CAS</li>
<li>ABA问题。(详情查看:<a href="https://www.sunjianbo.com/cas-aba/">https://www.sunjianbo.com/cas-aba/</a>)。</li>
</ol>
<h4 id="方式二-锁">方式二: 锁</h4>
<p>锁能保证可见性,是基于Happens-before原则</p>
<p>一个线程的unlock一定执行在另一个线程的lock前面,也就是说另一个线程不会插进该线程的执行过程(lock-&gt;op-&gt;unlock)中</p>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[Java CAS ABA问题发生的场景分析(转)]]></title>
        <id>https://www.sunjianbo.com/cas-aba/</id>
        <link href="https://www.sunjianbo.com/cas-aba/">
        </link>
        <updated>2020-08-25T07:23:23.000Z</updated>
        <content type="html"><![CDATA[<p>原文转自:<a href="https://www.cnblogs.com/senlinyang/p/7875381.html">https://www.cnblogs.com/senlinyang/p/7875381.html</a></p>
<p>CAS操作存在一个ABA问题，就是在CAS之前A变成B又变回A，CAS还是能够设置成功的，</p>
<p>什么场景下会出现这个问题呢？查了一些资料，发现在下面的两种情况下会出现ABA问题。</p>
<h2 id="1-a最开始的内存地址是x然后失效了有分配了b恰好内存地址是x这时候通过cas操作却设置成功了">1. A最开始的内存地址是X，然后失效了，有分配了B，恰好内存地址是X，这时候通过CAS操作，却设置成功了</h2>
<p>这种情况在带有GC的语言中，这种情况是不可能发生的，为什么呢？</p>
<p>拿JAVA举例，在执行CAS操作时，A，B对象肯定生命周期内，GC不可能将其释放，那么A指向的内存是不会被释放的，B也就不可能分配到与A相同的内存地址，CAS失败。若在无GC的，A对象已经被释放了，那么B被分配了A的内存，CAS成功。</p>
<h2 id="2-线程1准备用cas将变量的值由a替换为b在此之前线程2将变量的值由a替换为c又由c替换为a然后线程1执行cas时发现变量的值仍然为a所以cas成功">2. 线程1准备用CAS将变量的值由A替换为B，在此之前，线程2将变量的值由A替换为C，又由C替换为A，然后线程1执行CAS时发现变量的值仍然为A，所以CAS成功。</h2>
<p>但实际上这时的现场已经和最初不同了，尽管CAS成功，但可能存在潜藏的问题。</p>
<p>比如：现有一个用单向链表实现的堆栈，栈顶为A，这时线程T1已经知道A.next为B，然后希望用CAS将栈顶替换为B：head.compareAndSet(A,B);在T1执行上面这条指令之前，线程T2介入，将A、B出栈，再pushD、C、A。而对象B此时处于游离状态：此时轮到线程T1执行CAS操作，检测发现栈顶仍为A，所以CAS成功，栈顶变为B，但实际上B.next为null，其中堆栈中只有B一个元素，C和D组成的链表不再存在于堆栈中，平白无故就把C、D丢掉了。</p>
<p>以上就是由于ABA问题带来的隐患，各种乐观锁的实现中通常都会用版本戳version来对记录或对象标记，避免并发操作带来的问题</p>
<p>在Java中，<code>AtomicStampedReference&lt;E&gt;</code>也实现了这个作用，它通过包装[E,Integer]的元组来对对象标记版本戳stamp，从而避免ABA问题，</p>
<p>例如下面的代码分别用AtomicInteger和AtomicStampedReference来对初始值为100的原子整型变量进行更新，AtomicInteger会成功执行CAS操作，而加上版本戳的AtomicStampedReference对于ABA问题会执行CAS失败</p>
<pre><code class="language-java">package concur.lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;

public class ABA {
    
    private static AtomicInteger atomicInt = new AtomicInteger(100);
    private static AtomicStampedReference&lt;Integer&gt; atomicStampedRef = 
            new AtomicStampedReference&lt;Integer&gt;(100, 0);
    
    public static void main(String[] args) throws InterruptedException {
        Thread intT1 = new Thread(new Runnable() {
            @Override
            public void run() {
                atomicInt.compareAndSet(100, 101);
                atomicInt.compareAndSet(101, 100);
            }
        });
        
        Thread intT2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                boolean c3 = atomicInt.compareAndSet(100, 101);
                System.out.println(c3);        //true
            }
        });
        
        intT1.start();
        intT2.start();
        intT1.join();
        intT2.join();
        
        Thread refT1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                atomicStampedRef.compareAndSet(100, 101, 
                        atomicStampedRef.getStamp(), atomicStampedRef.getStamp()+1);
                atomicStampedRef.compareAndSet(101, 100, 
                        atomicStampedRef.getStamp(), atomicStampedRef.getStamp()+1);
            }
        });
        
        Thread refT2 = new Thread(new Runnable() {
            @Override
            public void run() {
                int stamp = atomicStampedRef.getStamp();
                System.out.println(&quot;before sleep : stamp = &quot; + stamp);    // stamp = 0
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(&quot;after sleep : stamp = &quot; + atomicStampedRef.getStamp());//stamp = 1
                boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp+1);
                System.out.println(c3);        //false
            }
        });
        
        refT1.start();
        refT2.start();
    }

}
</code></pre>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[使用gitlab的webhook实现合并分支时在jira上打个标签的功能]]></title>
        <id>https://www.sunjianbo.com/webhook-jira/</id>
        <link href="https://www.sunjianbo.com/webhook-jira/">
        </link>
        <updated>2020-08-21T01:41:30.000Z</updated>
        <content type="html"><![CDATA[<p>公司通过gitlab托管代码,采用jira作为敏捷实践的管理工具,每次发布的时候拉出当前迭代的所有故事做发布计划.</p>
<p>然而实际上因为某些原因,基本上无法实现每次迭代的所有故事都能发布.只有在分支上测试完成合并到 master上的故事才会被发布.</p>
<p>那么就想着能不能在分支合并到master的时候在jira上打个标签,这样在jira上一筛选就知道哪些故事是可以发布的了</p>
<p>好在gitlab有webhook功能,能够在触发某些操作的时候调用自定义的接口传输相关操作的信息,而jira也提供了相应的api来方便读写,那么只需要发布一个服务提供接口给webhook调用,解析传过来的参数,符合条件的情况下去调用jira的api修改标签,这个方案也就实现了.</p>
<h2 id="本文相关版本信息">本文相关版本信息</h2>
<blockquote>
<p>GitLab Community Edition 11.1.4</p>
<p>JIRA v7.13.5</p>
<p>python 3.5.4</p>
<p>Flask 1.1.2</p>
<p>requests 2.24.0</p>
</blockquote>
<h2 id="提前约定">提前约定</h2>
<p>在git创建分支时分支名称需要包含jiraId</p>
<p>我们项目组是直接以jiraId+故事标题命名分支</p>
<h2 id="编写程序">编写程序</h2>
<p>图快速方便所以用python写的,结果后面部署费了不少事</p>
<h3 id="webhook调用接口传递参数解析">webhook调用接口传递参数解析</h3>
<p>文档: https://docs.gitlab.com/ee/user/project/integrations/webhooks.html</p>
<p>参数是json形式的</p>
<p>主要取出其中3个参数</p>
<ul>
<li>
<p>object_attributes.state</p>
<p>gitlab设置的trigger是合并事件,实际上会在申请(opened),关闭(closed)和完成合并(merged)3个情况触发,所以要判断是否是完成合并的调用</p>
</li>
<li>
<p>object_attributes.target_branch</p>
<p>target_branch 是为了确认是合并到master的请求,合并到其他分支的请求不处理</p>
</li>
<li>
<p>object_attributes. source_branch</p>
<p>source_branch 是为了取出jiraId以进行后续操作</p>
</li>
</ul>
<h3 id="jira接口调用">jira接口调用</h3>
<p>文档: https://developer.atlassian.com/server/jira/platform/jira-rest-api-examples/#editing-an-issue-examples</p>
<p>从source_branch利用正则截取出jiraId,拼装并调用接口地址</p>
<p>附三个修改标签的传参,第一个是追加,后两种是覆盖设置</p>
<pre><code>#增加
{
    &quot;update&quot; : {
        &quot;labels&quot; : [{&quot;add&quot; : &quot;标签内容&quot;}]
    }
}
#设置
{
    &quot;fields&quot; : {
        &quot;labels&quot;: [&quot;标签内容&quot;]
        
        
    }
}
{
    &quot;update&quot; : {
        &quot;labels&quot; : [{&quot;set&quot; : [&quot;标签内容&quot;]}]
    }
}
</code></pre>
<h4 id="ps">PS:</h4>
<p>建议先用postman之类的工具先调试一下jira的接口,如果报错</p>
<blockquote>
<p>Field 'labels' cannot be set. It is not on the appropriate screen, or unknown.</p>
</blockquote>
<p>那就需要联系jira管理员,在项目的screens(项目管理-&gt;界面)中开启相关字段的api读写权限</p>
<h3 id="完整源码如下">完整源码如下:</h3>
<pre><code class="language-python"># -*- coding: utf-8 -*-

from flask import Flask,request
import requests
import re

baseUrl = 'http://jira.sunjianbo.com:8080/rest/api/2/issue/'
app = Flask(__name__)

@app.route(&quot;/&quot;, methods=[&quot;GET&quot;, &quot;POST&quot;])
def test():
    # addTag2Jira(&quot;DB19236-2398 324扥矿赛肯&quot;)
    return &quot;hello world&quot;

@app.route(&quot;/merged&quot;, methods=[&quot;GET&quot;, &quot;POST&quot;])
def merged():
    # print(request.headers)
    # print(request.json)
    # print('---')
    body = request.json
    object_attributes = body['object_attributes']
    state = object_attributes['state']  # opened,closed,merged
    source_branch = object_attributes['source_branch']
    target_branch = object_attributes['target_branch']
    if state == 'merged' and target_branch == 'master':
        # 已合并到master
        print('是已合并到master的请求,开始修改jira标签')
        addTag2Jira(source_branch)
    else:
        print('不是合并到master的成功请求,而是合并到['+target_branch+']的['+state+&quot;]请求&quot;)
    return &quot;&quot;

def addTag2Jira(source_branch):
    # 取得分支jira id
    matchObj = re.search(r&quot;DB\d+-\d+&quot;, source_branch)
    if matchObj:
        print(&quot;匹配出的jiraId: &quot;, matchObj.group())
        jiraId = matchObj.group()
        # 去相应jira故事中打一个uat标签
        url = baseUrl + jiraId
        messagebody = '''
            {
                &quot;update&quot; : {
                    &quot;labels&quot; : [{&quot;add&quot; : &quot;UAT&quot;}]
                }
            }
            '''
        result = fun_put(url, messagebody)
        print(result)
        if result.status_code == 204:
            print('修改成功: ' + url)
        else:
            print('修改失败: ' + source_branch)
    else:
        print(&quot;分支名称上找不到jiraId: &quot; + source_branch)

def fun_put(url, messagebody):
    headers = {'content-type': 'application/json', 'Accept-Charset': 'UTF-8'}
    r = requests.put(url, data=messagebody, headers=headers, auth=('username', 'passwd'))
    print(r.status_code)
    return r

if __name__ == &quot;__main__&quot;:
    app.run(host=&quot;0.0.0.0&quot;, port=8080)
</code></pre>
<h2 id="部署">部署</h2>
<p>部署这里有个插曲</p>
<p>公司服务器是离线的,所以我先离线安装了python3,然后打算安装virtualenv时报了一个ssl的错误,明明是离线安装,为毛会涉及到ssl,咱也不知道,咱也不敢问,推测是python3安装的有点问题,依赖的openssl之类的在测试机上不太全吧</p>
<p>折腾了半天没搞定,考虑到是测试服务器我就瞎搞了一把</p>
<p>用了</p>
<pre><code>pip3 install -r requirements.txt --proxy=代理服务器IP:端口号
</code></pre>
<p>代理走了我本地电脑在线安装了Flask和 requests,瞬间就解决了(给机智的我点个赞(◔◡◔))</p>
<h2 id="设置gitlab上的webhook">设置gitlab上的webhook</h2>
<p>settings-&gt;integrations-&gt;填写url-&gt;Trigger勾选<code>Merge request events</code>-&gt;嫌麻烦我禁用了ssl,没有实际测试一下行不行-&gt;保存</p>
<p><img src="https://static.sunjianbo.com/image-20200820160206463.png" alt="set-integrations" loading="lazy">)</p>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[JVM内存模型(理论很枯燥)]]></title>
        <id>https://www.sunjianbo.com/jmm/</id>
        <link href="https://www.sunjianbo.com/jmm/">
        </link>
        <updated>2020-08-20T07:52:51.000Z</updated>
        <content type="html"><![CDATA[<h2 id="多线程中的问题">多线程中的问题</h2>
<ol>
<li>所见非所得</li>
<li>无法肉眼去检测程序的准确性</li>
<li>不同的运行平台有不同的表现</li>
<li>错误很难重现</li>
</ol>
<h2 id="问题示例">问题示例</h2>
<p>将运行模式设置为-server， 变成死循环   。 没加默认就是client模式，就是正常（可见性问题）</p>
<pre><code class="language-java">import java.util.concurrent.TimeUnit;

// 1、 jre/bin/server  放置hsdis动态链接库
//  测试代码 将运行模式设置为-server， 变成死循环   。 没加默认就是client模式，就是正常（可见性问题）
// 2、 通过设置JVM的参数，打印出jit编译的内容 （这里说的编译非class文件），通过可视化工具jitwatch进行查看
// -server -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:+LogCompilation -XX:LogFile=jit.log
//  关闭jit优化-Djava.compiler=NONE
public class VisibilityDemo {
    private volatile boolean flag = true;

    public static void main(String[] args) throws InterruptedException {
        VisibilityDemo demo1 = new VisibilityDemo();
        Thread thread1 = new Thread(new Runnable() {
            public void run() {
                int i = 0;
                // class -&gt;  运行时jit编译  -&gt; 汇编指令 -&gt; 重排序
                while (demo1.flag) { // 指令重排序
                    i++;
                }
                System.out.println(i);
            }
        });
        thread1.start();

        TimeUnit.SECONDS.sleep(2);
        // 设置is为false，使上面的线程结束while循环
        demo1.flag = false;
        System.out.println(&quot;被置为false了.&quot;);
    }
}
</code></pre>
<h3 id="死循环可能的两个原因">死循环可能的两个原因:</h3>
<h4 id="cpu缓存">CPU缓存</h4>
<p>​	CPU缓存在这里会导致线程读取的flag延迟一些变为false,但不会发生死循环</p>
<figure data-type="image" tabindex="1"><img src="https://static.sunjianbo.com/image-20200813093851143.png" alt="工作内存缓存" loading="lazy"></figure>
<h4 id="指令重排序">指令重排序</h4>
<p>Java编程语言的语义允许**编译器和微处理器(JIT)**执行优化,这些优化可以与不正确的同步代码交互,从而产生看似矛盾的行为。</p>
<ol>
<li>执行顺序的重排序</li>
</ol>
<figure data-type="image" tabindex="2"><img src="https://static.sunjianbo.com/image-20200813094122201.png" alt="image-20200813094122201" loading="lazy"></figure>
<ol start="2">
<li>等效替换的重排序</li>
</ol>
<figure data-type="image" tabindex="3"><img src="https://static.sunjianbo.com/image-20200813094209877.png" alt="image-20200813094209877" loading="lazy"></figure>
<p>虽然有as-if- serial的原则,但是因为多CPU的情况就变得复杂了,每个cpu只能保证自己的重排序是没有问题的</p>
<h3 id="真正原因">真正原因</h3>
<p>汇编层面的重排序会将<code>while(demo1.flag)</code>变为</p>
<pre><code> if(demo1.flag){
     while(true){
         i++
     }
 }
</code></pre>
<p>它认为demo1.flag基本不变就是true</p>
<h3 id="附-jitwatch使用">附: JITWatch使用</h3>
<p>可直接参考这个操作博客<br>
http://www.cnblogs.com/stevenczp/p/7975776.html<br>
https://www.cnblogs.com/stevenczp/p/7978554.html</p>
<ol>
<li>
<p>输出jit日志</p>
<ol>
<li>（windows）在jre/bin/server  放置hsdis动态链接库</li>
<li>eclise、idea等工具，加上JVM参数<br>
-server -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:+LogCompilation -XX:LogFile=jit.log</li>
</ol>
</li>
<li>
<p>工具安装<br>
下载  https://github.com/AdoptOpenJDK/jitwatch</p>
</li>
<li>
<p>解压 通过maven运行</p>
</li>
</ol>
<pre><code>mvn clean compile exec:java
</code></pre>
<ol start="4">
<li>
<p>配置jitwatch<br>
页面选择 config， 配置要调试的项目src源码路径，和class编译路径<br>
打开jit.log<br>
点击start</p>
</li>
<li>
<p>在分析的结果中，选中指定的类，再选择右侧的具体方法，则弹出jit编译结果</p>
</li>
</ol>
<hr>
<p>为了解决多线程的问题,提出了一种规范,就是内存模型</p>
<h2 id="内存模型memory-model的定义">内存模型(Memory Model)的定义</h2>
<p>内存模型描述程序的可能行为。</p>
<p><strong>Java编程语言内存模型</strong>通过检查执行跟踪中的每个读操作,并根据某些规则检查该读操作观察到的写操作是否有效来工作。</p>
<p>要程序的所有执行产生的结果都可以由内存模型预测。具体的实现者任意实现,包括操作的重新排序和删除不必要的同步。</p>
<blockquote>
<p>内存模型决定了在程序的每个点上可以读取什么值</p>
<p>比如说线程1的操作让线程2能及时看到改变,也就是说在这个点上线程2可以且必须读到它应该读取的正确的值</p>
</blockquote>
<h3 id="shared-variables共享变量">Shared variables共享变量</h3>
<p>描述可以在线程之间共享的内存称为共享内存或堆内存。所有实例字段、静态字段和数组元素都存储在堆内存中。</p>
<p>如果至少有一个访问是写的,那么对同一个变量的两次访问(读或写)是冲突的。</p>
<p>这句话定义在:https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jis-17.4.1</p>
<h3 id="线程间操作的定义">线程间操作的定义</h3>
<ul>
<li>write要写的变量以及要写的值。</li>
<li>read要读的变量以及可见的写入值(由此,我们可以确定可见的值)</li>
<li>lock要锁定的管程(监视器 monitor)</li>
<li>unlock要解锁的管程。</li>
<li>外部操作(socket等等..)</li>
<li>启动和终止</li>
</ul>
<p>程序顺序: 如果一个程序没有数据竞争,那么程序的所有执行看起来都是顺序一致的</p>
<p>本规范只涉及线程间的操作</p>
<h3 id="对于同步的规则定义">对于同步的规则定义</h3>
<p><em>同步我理解就是对一些操作约定了固定的先后顺序,且后面能看到前面的操作</em></p>
<ul>
<li>对于监视器m的解锁与所有后续操作对于m的加锁同步</li>
<li>对 volatile变量v的写入,与所有其他线程后续对v的读同步</li>
<li>启动线程的操作与线程中的第一个操作同步</li>
<li>对于每个属性写入默认值(0, false,null)与每个线程对其进行的操作同步</li>
<li>线程T1的最后操作与线程T2发现线程T1已经结束同步(isAlive, join可以判断线程是否终结)</li>
<li>如果线程T1中断了T2,那么线程T1的中断操作与其他所有线程发现T2被中断了同步(通过抛出InterruptedException异常,或者调用Thread.interrupted或Thread.isInterrupted)</li>
</ul>
<h3 id="happens-before先行发生原则">Happens- before先行发生原则</h3>
<p>happens-before关系主要用于强调两个有冲突的动作之间的顺序,以及定义数据争用的发生时机。</p>
<p>具体的虚拟机实现,有必要确保以下原则的成立</p>
<ul>
<li>某个线程中的每个动作都 happens-before该线程中该动作后面的动作</li>
<li>某个管程上的 unlock动作 happens-before同一个管程上后续的lock动作</li>
<li>对某个 volatile字段的写操作 happens-before每个后续对该 volatile字段的读操作</li>
<li>在某个线程对象上调用 start()方法 happens-before该启动了的线程中的任意动作</li>
<li>某个线程中的所有动作 happens-before任意其它线程成功从该线程对象上的join()中返回</li>
<li>如果某个动作a happens-before动作b,且b happens-before动作c,则有 a happens-before c</li>
</ul>
<p>当程序包含两个没有被 happens-before关系排序的冲突访问时,就称存在<strong>数据争用</strong></p>
<p>遵守了这个原则,也就意味着有些代码不能进行重排序,有些数据不能缓存!</p>
<h2 id="一些实践">一些实践</h2>
<h3 id="volatile关键字">volatile关键字</h3>
<p>可见性问题:让一个线程对共享变量的修改,能够及时的被其他线程看到。</p>
<blockquote>
<p>根据JMM中规定的 happen before和同步原则:</p>
<p>对某个 volatile字段的写操作 happens-before每个后续对该 volatile字段的读操作。</p>
<p>对 volatile变量v的写入,与所有其他线程后续对v的读同步</p>
</blockquote>
<p>要满足这些条件,所以 volatile关键字就有这些功能:</p>
<ol>
<li>
<p>禁止缓存:</p>
<p>volatile变量的访问控制符会加个 ACC VOLATILE</p>
<p>https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#tjvms-4.5</p>
</li>
<li>
<p>对 volatile变量相关的指令不做重排序</p>
</li>
</ol>
<h3 id="fina在jmm中的处理">fina在JMM中的处理</h3>
<ul>
<li>final在该对象的构造函数中设置对象的字段,当线程看到该对象时,将始终看到该对象的final字段的正确构造版本。<br>
伪代码示例: <code>f= new finalDemo();</code>读取到的f.x一定最新,x为final字段。而y可能是0</li>
</ul>
<pre><code class="language-java">// 官方示例，可能会读取到y的值为0
class FinalFieldExample {
    final int x;
    int y;
    static FinalFieldExample f;

    public FinalFieldExample() {
        x = 3;
        y = 4;
    }

    static void writer() {
        f = new FinalFieldExample();
    }

    static void reader() {
        if (f != null) {
            int i = f.x;  // guaranteed to see 3 肯定是3
            int j = f.y;  // could see 0 可能看到0
        }
    }
}
</code></pre>
<ul>
<li>
<p>如果在构造函数中设置字段后发生读取,则会看到该final字段分配的值,否则它将看到默认值;<br>
伪代码示例: <code>public finalDemo(){x=1;y=x;};</code>y会等于1;</p>
</li>
<li>
<p>读取该共享对象的final成员变量之前,先要读取共享对象。<br>
伪代码示例:<code>r = new ReferenceObj();k=r.f;</code>这两个操作不能重排序</p>
</li>
<li>
<p>通常<code>static final</code>是不可以修改的字段。然而<code>System.in</code>,<code>System.out</code>和 <code>System.err</code>是 <code>static final</code>字段,遗留原因,必须允许通过set方法改变,我们将这些字段称为写保护,以区别于普通final字段;</p>
</li>
</ul>
<h3 id="word-tearing字节处理">Word Tearing字节处理</h3>
<p>一个字段或元素的更新不得与任何其他字段或元素的读取或更新交互。</p>
<p>特别是,分别更新字节数组的相邻元素的两个线程不得干涉或交互,也不需要同步以确保顺序一致性。</p>
<p>有些处理器(尤其是早期的Alphas处理器)没有提供写单个字节的功能</p>
<p>在这样的处理器上更新byte数组,若只是简单地读取整个内容,更新对应的字节,然后将整个内容再写回内存,将是不合法的。</p>
<p>这个问题有时候被称为**“字分裂 word tearing)”**,在单独更新单个字节有难度的处理器上,就需要寻求其它方式了。</p>
<p>基本不需要考虑这个,了解就好。</p>
<pre><code class="language-java">// https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.4
// 官方提供的示例，检查有没有WordTearing情况
public class WordTearing extends Thread {
    static final int LENGTH = 8;
    static final int ITERS = 1000000;
    static byte[] counts = new byte[LENGTH];
    static Thread[] threads = new Thread[LENGTH];

    final int id;

    WordTearing(int i) {
        id = i;
    }

    public void run() {
        byte v = 0;
        for (int i = 0; i &lt; ITERS; i++) {
            byte v2 = counts[id];
            if (v != v2) {
                System.err.println(&quot;Word-Tearing found: &quot; +
                        &quot;counts[&quot; + id + &quot;] = &quot; + v2 +
                        &quot;, should be &quot; + v);
                return;
            }
            v++;
            counts[id] = v;
        }
    }

    public static void main(String[] args) {
        for (int i = 0; i &lt; LENGTH; ++i)
            (threads[i] = new WordTearing(i)).start();
    }
}
</code></pre>
<h3 id="double和long的特殊处理">double和long的特殊处理</h3>
<p>虚拟机规范中,写64位的 double和long分成了两次32位值的操作</p>
<p>由于不是原子操作,可能导致读取到某次写操作中64位的前32位,以及另外一次写操作的后32位</p>
<figure data-type="image" tabindex="4"><img src="https://static.sunjianbo.com/image-20200814163308291.png" alt="image-20200814163308291" loading="lazy"></figure>
<p>读写 volatile的long和 double总是原子的。读写引用也总是原子的</p>
<blockquote>
<p>商业JⅥM不会存在这个问题,虽然规范没要求实现原子性,但是考虑到实际应用,大部分都实现了原子性。</p>
</blockquote>
]]></content>
    </entry>
    <entry>
        <title type="html"><![CDATA[线程池应用及实现原理剖析]]></title>
        <id>https://www.sunjianbo.com/thread-pool/</id>
        <link href="https://www.sunjianbo.com/thread-pool/">
        </link>
        <updated>2020-08-17T07:30:17.000Z</updated>
        <content type="html"><![CDATA[<h2 id="为什么要用线程池">为什么要用线程池</h2>
<p>线程是不是越多越好?</p>
<ol>
<li>线程在java中是一个对象,更是操作系统的资源,线程创建、销毁需要时间。如果创建时间+销毁时间&gt;执行任务时间就很不合算。</li>
<li>java对象占用堆内存,操作系统线程占用系统内存,根据jνm规范,一个线程默认最大栈大小1M,这个栈空间是需要从系统内存中分配的。线程过多,会消耗很多的內存。</li>
<li>操作系统需要频繁切换线程上下文(大家都想被运行),影响性能。</li>
</ol>
<p>线程池的推出,就是为了方便的控制线程数量。</p>
<h2 id="线程池原理-概念">线程池原理 - 概念</h2>
<h4 id="线程池管理器">线程池管理器</h4>
<p>用于创建并管理线程池,包括创建线程池,销毁线程池,添加新任务;</p>
<h4 id="工作线程">工作线程</h4>
<p>线程池中线程,在没有任务时处于等待状态,可以循环的执行任务;</p>
<h4 id="任务接口">任务接口</h4>
<p>每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口,任务执行完后的收尾工作,任务的执行状态等;</p>
<h4 id="任务队列">任务队列</h4>
<p>用于存放没有处理的任务。提供一种缓冲机制。</p>
<figure data-type="image" tabindex="1"><img src="https://static.sunjianbo.com/image-20200805095038062.png" alt="" loading="lazy"></figure>
<h2 id="线程池api-接口定义和实现类">线程池API - 接口定义和实现类</h2>
<table>
<thead>
<tr>
<th>类型</th>
<th>名称</th>
<th>描述</th>
</tr>
</thead>
<tbody>
<tr>
<td>接口</td>
<td>Executor</td>
<td>最上层的接口,定义了<font color=red>执行任务的方法 execute</font></td>
</tr>
<tr>
<td>接口</td>
<td>ExecutorService</td>
<td>继承了 Executor接口,拓展了 Callable、 Future、关闭方法</td>
</tr>
<tr>
<td>接口</td>
<td>ScheduledExecutorService</td>
<td>继承了 ExecutorService,增加了定时任务相关的方法</td>
</tr>
<tr>
<td>实现类</td>
<td>ThreadPoolExecutor</td>
<td><font color=red>基础、标准的线程池实现</font></td>
</tr>
<tr>
<td>实现类</td>
<td>ScheduledThreadPoolExecutor</td>
<td>继承了 ThreadPoolExecutor,实现了ScheduledExecutorService中相关<font color=red>定时任务</font>的方法</td>
</tr>
</tbody>
</table>
<h2 id="线程池api-方法定义">线程池API - 方法定义</h2>
<h3 id="executorservice">ExecutorService</h3>
<pre><code class="language-java">//监测ExecutorService是否已经关闭， 直到所有任务完成执行，或超时发生，或当前线程被中断
awaitTermination(long timeout, TimeUnit unit)
//执行给定的任务集合，执行完毕后，返回结果
invokeAll(Collection&lt;? extends Callable&lt;T&gt;&gt; tasks)
//执行给定的任务集合，执行完毕或者超时后，返回结果，其他任务终止
invokeAll(Collection&lt;? extends Callable&lt;T&gt;&gt; tasks, long timeout, TimeUnit unit)
//执行给定的任务，任意一个执行成功则返回结果，其他任务终止
invokeAny(Collection&lt;? extends Callable&lt;T&gt;&gt; tasks)
//执行给定的任务，任意一个执行成功或者超时后，则返回结果，其他任务终止
invokeAny(Collection&lt;? extends Callable&lt;T&gt;&gt; tasks, long timeout, TimeUnit unit)
//如果此线程池已关闭，则返回true。
isShutdown()
//如果关闭后所有任务都已完成，则返回true。
isTerminated()
//优雅关闭线程池，之前提交的任务将被执行，但是不会接受新的任务。
shutdown()
//尝试停止所有正在执行的任务，停止等待任务的处理，并返回等待执行任务的列表。
shutdownNow()
//提交个用于执行的Callable返回任务，并返回一个Future,用于获取Callable执行结果
submit(Callable&lt;T&gt; task)
//提交可运行任务以执行，并发回一个Future对象，执行结果为null
submit(Runnable task)
//提交可运行任务以执行，并返回Future, 执行结果为传入的result
submit(Runnable task, T result)
</code></pre>
<h3 id="scheduledexecutorservice">ScheduledExecutorService</h3>
<ul>
<li>
<p><code>schedule(Callable&lt;V&gt; callable, long delay, TimeUnit unit)</code></p>
<p>创建并执行一个一次性任务,过了延迟时间就会被执行</p>
</li>
<li>
<p><code>schedule(Callable&lt;V&gt; callable, long delay, TimeUnit unit)</code></p>
<p>创建并执行一个一次性任务,过了延迟时间就会被执行</p>
</li>
<li>
<p><code>scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)</code></p>
<p>创建并执行一个周期性任务过了给定的初始延迟时间,会第一次被执行</p>
<p>执行过程中发生了异常,那么任务就停止</p>
<p>一次任务执行时长超过了周期时间,下一次任务会等到该次任务执行结束后,立刻执行,这也是它和<code>scheduleWithFixedDelay</code>的重要区别。<br>
此处结合代码示例进行理解即可!</p>
</li>
<li>
<p><code>scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit)</code></p>
<p>创建并执行一个周期性任务,过了初始延迟时间,第一次被执行,后续以给定的周期时间执行</p>
<p>执行过程中发生了异常,那么任务就停止</p>
<p>一次任务执行时长超过了周期时间,下一次任务会在该次任务执行结束的时间基础上,计算执行延时。</p>
<p>对于超过周期的长时间处理任务的不同处理方式,这是它和<code>scheduleAtFixedRate</code>的重要区别。</p>
</li>
</ul>
<h2 id="线程池api-executors工具类">线程池API - Executors工具类</h2>
<p>你可以自己实例化线程池,也可以用 Executors创建线程池的工厂类,常用方法如下:</p>
<h3 id="newfixedthreadpoolint-nthreads">newFixedThreadPool(int nThreads)</h3>
<p>创建一个固定大小、任务队列容量无界的线程池。核心线程数=最大线程数。</p>
<h3 id="newcachedthreadpool">newCachedThreadPool()</h3>
<p>创建的是一个大小无界的缓冲线程池。它的任务队列是一个同步队列。</p>
<p>任务加入到池中,如果池中有空闲线程,则用空闲线程执行,如无则创建新线程执行。</p>
<p>池中的线程空闲超过60秒,将被销毁释放。线程数随任务的多少变化。</p>
<p>适用于执行耗时较小的异步任务。池的核心线程数=0,最大线程数= <code>Integer.MAX_VALUE</code></p>
<h3 id="newsinglethreadexecutor">newSingleThreadExecutor()</h3>
<p>只有一个线程来执行无界任务队列的单一线程池。</p>
<p>该线程池确保任务按加入的顺序一个个依次执行。当唯一的线程因任务异常中止时,将创建一个新的线程来继续执行后续的任务。</p>
<p>与 <code>newFixedThreadPool(1)</code>的区别在于,单一线程池的池大小在<code>newSingleThreadExecutor</code>方法中硬编码,不能再改变的。</p>
<h3 id="newscheduledthreadpoolint-corepoolsize">newScheduledThreadPool(int corePoolSize)</h3>
<p>能定时执行任务的线程池。该池的核心线程数由参数指定,最大线程数= <code>Integer.MAX_VALUE</code></p>
<h2 id="线程池原理-任务execute过程">线程池原理-任务execute过程</h2>
<ol>
<li>
<p>是否达到核心线程数量?</p>
<p>没达到,创建一个工作线程来执行任务。</p>
</li>
<li>
<p>工作队列是否已满?</p>
<p>没满,则将新提交的任务存储在工作队列里。</p>
</li>
<li>
<p>是否达到线程池最大数量?</p>
<p>没达到,则创建一个新的工作线程来执行任务。</p>
</li>
<li>
<p>最后,执行拒绝策略来处理这个任务。</p>
</li>
</ol>
<figure data-type="image" tabindex="2"><img src="https://static.sunjianbo.com/image-20200806112820086.png" alt="" loading="lazy"></figure>
<h2 id="实践-合适的线程数量">实践 - 合适的线程数量</h2>
<p>如何确定合适数量的线程?</p>
<ul>
<li>
<p>计算型任务:</p>
<p>CPU数量的1-2倍</p>
</li>
<li>
<p>IO型任务:</p>
<p>相对比计算型任务,需多一些线程,要根据具体的<u>IO阻塞时长</u>进行考量决定。<br>
如 tomcat中默认的最大线程数为: 200。</p>
</li>
</ul>
<p>也可考虑根据需要在一个<u>最小数量和最大数量间</u>自动增减线程数。(<code>newCachedThreadPool</code>)</p>
<h2 id="代码示例">代码示例:</h2>
<pre><code class="language-java">import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/** 线程池的使用 */
public class Demo9 {

	/**
	 * 测试： 提交15个执行时间需要3秒的任务,看线程池的状况
	 * 
	 * @param threadPoolExecutor 传入不同的线程池，看不同的结果
	 * @throws Exception
	 */
	public void testCommon(ThreadPoolExecutor threadPoolExecutor) throws Exception {
		// 测试： 提交15个执行时间需要3秒的任务，看超过大小的2个，对应的处理情况
		for (int i = 0; i &lt; 15; i++) {
			int n = i;
			threadPoolExecutor.submit(new Runnable() {
				@Override
				public void run() {
					try {
						System.out.println(&quot;开始执行：&quot; + n);
						Thread.sleep(3000L);
						System.err.println(&quot;执行结束:&quot; + n);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			});
			System.out.println(&quot;任务提交成功 :&quot; + i);
		}
		// 查看线程数量，查看队列等待数量
		Thread.sleep(500L);
		System.out.println(&quot;当前线程池线程数量为：&quot; + threadPoolExecutor.getPoolSize());
		System.out.println(&quot;当前线程池等待的数量为：&quot; + threadPoolExecutor.getQueue().size());
		// 等待15秒，查看线程数量和队列数量（理论上，会被超出核心线程数量的线程自动销毁）
		Thread.sleep(15000L);
		System.out.println(&quot;当前线程池线程数量为：&quot; + threadPoolExecutor.getPoolSize());
		System.out.println(&quot;当前线程池等待的数量为：&quot; + threadPoolExecutor.getQueue().size());
	}

	/**
	 * 标准线程池
	 * 1、线程池信息： 核心线程数量5，最大数量10，无界队列，超出核心线程数量的线程存活时间：5秒， 指定拒绝策略的
	 * 
	 * @throws Exception
	 */
	private void threadPoolExecutorTest1() throws Exception {
		ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS,
				new LinkedBlockingQueue&lt;Runnable&gt;());

		testCommon(threadPoolExecutor);
		// 预计结果：线程池线程数量为：5,超出数量的任务，其他的进入队列中等待被执行
	}

	/**
	 * 有拒绝策略的
	 * 2、 线程池信息： 核心线程数量5，最大数量10，队列大小3，超出核心线程数量的线程存活时间：5秒， 指定拒绝策略的
	 * 
	 * @throws Exception
	 */
	private void threadPoolExecutorTest2() throws Exception {
		// 创建一个 核心线程数量为5，最大数量为10,等待队列最大是3 的线程池，也就是最大容纳13个任务。
		// 默认的策略是抛出RejectedExecutionException异常，java.util.concurrent.ThreadPoolExecutor.AbortPolicy
		ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS,
				new LinkedBlockingQueue&lt;Runnable&gt;(3), new RejectedExecutionHandler() {
					@Override
					public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
						System.err.println(&quot;有任务被拒绝执行了&quot;);
					}
				});
		testCommon(threadPoolExecutor);
		// 预计结果：
		// 1、 5个任务直接分配线程开始执行
		// 2、 3个任务进入等待队列
		// 3、 队列不够用，临时加开5个线程来执行任务(5秒没活干就销毁)
		// 4、 队列和线程池都满了，剩下2个任务，没资源了，被拒绝执行。
		// 5、 任务执行，5秒后，如果无任务可执行，销毁临时创建的5个线程
	}

	/**
	 * 自定义Executors.newFixedThreadPool(int nThreads)
	 * 3、 线程池信息： 核心线程数量5，最大数量5，无界队列，超出核心线程数量的线程存活时间：5秒
	 * 
	 * @throws Exception
	 */
	private void threadPoolExecutorTest3() throws Exception {
		// 和Executors.newFixedThreadPool(int nThreads)一样的
		ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS,
				new LinkedBlockingQueue&lt;Runnable&gt;());
		testCommon(threadPoolExecutor);
		// 预计结：线程池线程数量为：5，超出数量的任务，其他的进入队列中等待被执行
	}

	/**
	 * 自定义Executors.newCachedThreadPool()
	 * 4、 线程池信息：
	 * 核心线程数量0，最大数量Integer.MAX_VALUE，SynchronousQueue队列，超出核心线程数量的线程存活时间：60秒
	 * 
	 * @throws Exception
	 */
	private void threadPoolExecutorTest4() throws Exception {

		// SynchronousQueue，实际上它不是一个真正的队列，因为它不会为队列中元素维护存储空间。与其他队列不同的是，它维护一组线程，这些线程在等待着把元素加入或移出队列。
		// 在使用SynchronousQueue作为工作队列的前提下，客户端代码向线程池提交任务时，
		// 而线程池中又没有空闲的线程能够从SynchronousQueue队列实例中取一个任务，
		// 那么相应的offer方法调用就会失败（即任务没有被存入工作队列）。
		// 此时，ThreadPoolExecutor会新建一个新的工作者线程用于对这个入队列失败的任务进行处理（假设此时线程池的大小还未达到其最大线程池大小maximumPoolSize）。

		// 和Executors.newCachedThreadPool()一样的
		ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS,
				new SynchronousQueue&lt;Runnable&gt;());
		testCommon(threadPoolExecutor);
		// 预计结果：
		// 1、 线程池线程数量为：15，超出数量的任务，其他的进入队列中等待被执行
		// 2、 所有任务执行结束，60秒后，如果无任务可执行，所有线程全部被销毁，池的大小恢复为0
		Thread.sleep(60000L);
		System.out.println(&quot;60秒后，再看线程池中的数量：&quot; + threadPoolExecutor.getPoolSize());
	}

	/**
	 * 延迟执行
	 * 5、 定时执行线程池信息：3秒后执行，一次性任务，到点就执行 &lt;br/&gt;
	 * 核心线程数量5，最大数量Integer.MAX_VALUE，DelayedWorkQueue延时队列，超出核心线程数量的线程存活时间：0秒
	 * 
	 * @throws Exception
	 */
	private void threadPoolExecutorTest5() throws Exception {
		// 和Executors.newScheduledThreadPool()一样的
		ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor(5);
		threadPoolExecutor.schedule(new Runnable() {
			@Override
			public void run() {
				System.out.println(&quot;任务被执行，现在时间：&quot; + System.currentTimeMillis());
			}
		}, 3000, TimeUnit.MILLISECONDS);
		System.out.println(
				&quot;定时任务，提交成功，时间是：&quot; + System.currentTimeMillis() + &quot;, 当前线程池中线程数量：&quot; + threadPoolExecutor.getPoolSize());
		// 预计结果：任务在3秒后被执行一次
	}

	/**
	 * 周期性执行
	 * 6、 定时执行线程池信息：线程固定数量5 ，&lt;br/&gt;
	 * 核心线程数量5，最大数量Integer.MAX_VALUE，DelayedWorkQueue延时队列，超出核心线程数量的线程存活时间：0秒
	 * 
	 * @throws Exception
	 */
	private void threadPoolExecutorTest6() throws Exception {
		ScheduledThreadPoolExecutor threadPoolExecutor = new ScheduledThreadPoolExecutor(5);
		// 周期性执行某一个任务，线程池提供了两种调度方式，这里单独演示一下。测试场景一样。
		// 测试场景：提交的任务需要3秒才能执行完毕。看两种不同调度方式的区别
		// 效果1： 提交后，2秒后开始第一次执行，之后每间隔1秒，固定执行一次(如果发现上次执行还未完毕，则等待完毕，完毕后立刻执行)。
		// 也就是说这个代码中是，3秒钟执行一次（计算方式：每次执行三秒，间隔时间1秒，执行结束后马上开始下一次执行，无需等待）
		threadPoolExecutor.scheduleAtFixedRate(new Runnable() {
			@Override
			public void run() {
				try {
					Thread.sleep(3000L);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(&quot;任务-1 被执行，现在时间：&quot; + System.currentTimeMillis());
			}
		}, 2000, 1000, TimeUnit.MILLISECONDS);

		// 效果2：提交后，2秒后开始第一次执行，之后每间隔1秒，固定执行一次(如果发现上次执行还未完毕，则等待完毕，等上一次执行完毕后再开始计时，等待1秒)。
		// 也就是说这个代码钟的效果看到的是：4秒执行一次。 （计算方式：每次执行3秒，间隔时间1秒，执行完以后再等待1秒，所以是 3+1）
		threadPoolExecutor.scheduleWithFixedDelay(new Runnable() {
			@Override
			public void run() {
				try {
					Thread.sleep(3000L);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(&quot;任务-2 被执行，现在时间：&quot; + System.currentTimeMillis());
			}
		}, 2000, 1000, TimeUnit.MILLISECONDS);
	}

	/**
	 * 7、 终止线程：线程池信息： 核心线程数量5，最大数量10，队列大小3，超出核心线程数量的线程存活时间：5秒， 指定拒绝策略的
	 * 
	 * @throws Exception
	 */
	private void threadPoolExecutorTest7() throws Exception {
		// 创建一个 核心线程数量为5，最大数量为10,等待队列最大是3 的线程池，也就是最大容纳13个任务。
		// 默认的策略是抛出RejectedExecutionException异常，java.util.concurrent.ThreadPoolExecutor.AbortPolicy
		ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS,
				new LinkedBlockingQueue&lt;Runnable&gt;(3), new RejectedExecutionHandler() {
					@Override
					public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
						System.err.println(&quot;有任务被拒绝执行了&quot;);
					}
				});
		// 测试： 提交15个执行时间需要3秒的任务，看超过大小的2个，对应的处理情况
		for (int i = 0; i &lt; 15; i++) {
			int n = i;
			threadPoolExecutor.submit(new Runnable() {
				@Override
				public void run() {
					try {
						System.out.println(&quot;开始执行：&quot; + n);
						Thread.sleep(3000L);
						System.err.println(&quot;执行结束:&quot; + n);
					} catch (InterruptedException e) {
						System.out.println(&quot;异常：&quot; + e.getMessage());
					}
				}
			});
			System.out.println(&quot;任务提交成功 :&quot; + i);
		}
		// 1秒后终止线程池
		Thread.sleep(1000L);
		threadPoolExecutor.shutdown();
		// 再次提交提示失败
		threadPoolExecutor.submit(new Runnable() {
			@Override
			public void run() {
				System.out.println(&quot;追加一个任务&quot;);
			}
		});
		// 结果分析
		// 1、 10个任务被执行，3个任务进入队列等待，2个任务被拒绝执行
		// 2、调用shutdown后，不接收新的任务，等待13任务执行结束
		// 3、 追加的任务在线程池关闭后，无法再提交，会被拒绝执行
	}

	/**
	 * 8、 立刻终止线程：线程池信息： 核心线程数量5，最大数量10，队列大小3，超出核心线程数量的线程存活时间：5秒， 指定拒绝策略的
	 * 
	 * @throws Exception
	 */
	private void threadPoolExecutorTest8() throws Exception {
		// 创建一个 核心线程数量为5，最大数量为10,等待队列最大是3 的线程池，也就是最大容纳13个任务。
		// 默认的策略是抛出RejectedExecutionException异常，java.util.concurrent.ThreadPoolExecutor.AbortPolicy
		ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 10, 5, TimeUnit.SECONDS,
				new LinkedBlockingQueue&lt;Runnable&gt;(3), new RejectedExecutionHandler() {
					@Override
					public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
						System.err.println(&quot;有任务被拒绝执行了&quot;);
					}
				});
		// 测试： 提交15个执行时间需要3秒的任务，看超过大小的2个，对应的处理情况
		for (int i = 0; i &lt; 15; i++) {
			int n = i;
			threadPoolExecutor.submit(new Runnable() {
				@Override
				public void run() {
					try {
						System.out.println(&quot;开始执行：&quot; + n);
						Thread.sleep(3000L);
						System.err.println(&quot;执行结束:&quot; + n);
					} catch (InterruptedException e) {
						System.out.println(&quot;异常：&quot; + e.getMessage());
					}
				}
			});
			System.out.println(&quot;任务提交成功 :&quot; + i);
		}
		// 1秒后终止线程池
		Thread.sleep(1000L);
		List&lt;Runnable&gt; shutdownNow = threadPoolExecutor.shutdownNow();
		// 再次提交提示失败
		threadPoolExecutor.submit(new Runnable() {
			@Override
			public void run() {
				System.out.println(&quot;追加一个任务&quot;);
			}
		});
		System.out.println(&quot;未结束的任务有：&quot; + shutdownNow.size());

		// 结果分析
		// 1、 10个任务被执行，3个任务进入队列等待，2个任务被拒绝执行
		// 2、调用shutdownnow后，队列中的3个线程不再执行，10个线程被终止
		// 3、 追加的任务在线程池关闭后，无法再提交，会被拒绝执行
	}

	public static void main(String[] args) throws Exception {
		new Demo9().threadPoolExecutorTest1();
//		new Demo9().threadPoolExecutorTest2();
//		new Demo9().threadPoolExecutorTest3();
//		new Demo9().threadPoolExecutorTest4();
//		new Demo9().threadPoolExecutorTest5();
//		new Demo9().threadPoolExecutorTest6();
//		new Demo9().threadPoolExecutorTest7();
//		new Demo9().threadPoolExecutorTest8();
	}
}
</code></pre>
]]></content>
    </entry>
</feed>