并发日记

Synchronized 是 由 JVM 实 现 的 一 种 实 现 互 斥 同 步 的 一 种 方 式 , 如 果
你 查 看 被 Synchronized 修 饰 过 的 程 序 块 编 译 后 的 字 节 码 , 会 发 现 ,
被 Synchronized 修 饰 过 的 程 序 代码块 , 在 编 译 前 后 被 编 译 器 生 成 了
monitorenter 和 monitorexit 两 个 字 节 码 指 令 。
这 两 个 指 令 是 什 么 意 思 呢 ?
在 虚 拟 机 执 行 到 monitorenter 指 令 时 , 首 先 要 尝 试 获 取 对 象 的 锁 :
如 果 这 个 对 象 没 有 锁 定 , 或 者 当 前 线 程 已 经 拥 有 了 这 个 对 象 的 锁 , 把 锁
的 计 数 器 +1; 当 执 行 monitorexit 指 令 时 将 锁 计 数 器 -1; 当 计 数 器
为 0 时 , 锁 就 被 释 放 了 。
如 果 获 取 对 象 失 败 了 , 那 当 前 线 程 就 要 阻 塞 等 待 , 直 到 对 象 锁 被 另 外 一
个 线 程 释 放 为 止 。
Java 中 Synchronize 通 过 在 对 象 头 设 置 标 记 , 达 到 了 获 取 锁 和 释 放
锁 的 目 的
而对于synchronized修饰的类对象或者方法,则会在flags表示上面增加ACC_SYNCHRONIZED 访问标志如果设置了,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor
锁的本质和如何确定对象的锁:

“ 锁 ” 的 本 质 其 实 是 monitorenter 和 monitorexit 字 节 码 指 令 的 一
个 Reference 类 型 的 参 数 , 即 要 锁 定 和 解 锁 的 对 象 。 我 们 知 道 , 使 用
Synchronized 可 以 修 饰 不 同 的 对 象 , 因 此 , 对 应 的 对 象 锁 可 以 这 么 确

如 果 Synchronized 明 确 指 定 了 锁 对 象 , 比 如 Synchronized( 变 量
名 ) 、 Synchronized(this) 等 , 说 明 加 解 锁 对 象 为 该 对 象 。

如 果 没 有 明 确 指 定 :
若 Synchronized 修 饰 的 方 法 为 非 静 态 方 法 , 表 示 此 方 法 对 应 的 对 象 为
锁 对 象 ;
若 Synchronized 修 饰 的 方 法 为 静 态 方 法 , 则 表 示 此 方 法 对 应 的 类 对 象
为 锁 对 象 。
注 意 , 当 一 个 对 象 被 锁 住 时 , 对 象 里 面 所 有 用 Synchronized 修 饰 的
方 法 都 将 产 生 堵 塞 , 而 对 象 里 非 Synchronized 修 饰 的 方 法 可 正 常 被
调 用 , 不 受 锁 影 响 。

什么是锁的可重入性:
可 重 入 性 是 锁 的 一 个 基 本 要 求 , 是 为 了 解 决 自 己 锁 死 自 己 的 情 况
为什么说synchronized是可重入锁:
因为,当有两个被synchronized修饰的方法互相调用时,如:

    synchronized void test1(){
        
    }
    synchronized void test2(){
        test1();
    }

在进入到test2时,获取到锁,然后在对test1进行调用时,就必须对当前锁的计数器加一操作,而不是test2已经有锁了,就不去获取test1的锁,应该是重复去获取一次test1的锁,对 Synchronized 来 说 , 可 重 入 性 是 显 而 易 见 的 , 刚 才 提 到 , 在 执 行monitorenter 指 令 时 , 如 果 这 个 对 象 没 有 锁 定 , 或 者 当 前 线 程 已 经 拥有 了 这 个 对 象 的 锁 ( 而 不 是 已 拥 有 了 锁 则 不 能 继 续 获 取 ) , 就 把 锁 的 计数 器 +1, 其 实 本 质 上 就 通 过 这 种 方 式 实 现 了 可 重 入 性 。

JVM对Java的原生锁做了哪些优化:
自旋锁:由于老的Java中的锁对象将一个线程阻塞或者唤醒,都需要操作系统进行协助,将线程从用户态切换到内核态来执行,这样就十分的消耗资源,经过优化后就出现了自旋锁,就是,当要将一个线程阻塞时,先让线程自旋等待一段时间,在等待的过程中 可能其他的线程都已经解锁释放了资源,这样就无需将线程置为阻塞状态,可以直接获取资源了。

偏 向 锁 ( Biased Locking)
轻 量 级 锁
轻量级锁乐观的认为,线程的竞争并不激烈,不会出现多个线程竞争锁资源,就算出现竞争,通过CAS操作或者自旋操作也能解决
重 量 级 锁
多个线程竞争锁资源非常激烈,重量级锁会使除了当前线程外的其余竞争线程全部陷入阻塞,等待唤醒
这 三 种 锁 使 得 JDK 得 以 优 化 Synchronized 的 运 行 , 当 JVM 检 测
到 不 同 的 竞 争 状 况 时 , 会 自 动 切 换 到 适 合 的 锁 实 现 , 这 就 是 锁 的 升 级 、
降 级 。
 当 没 有 竞 争 出 现 时 , 默 认 会 使 用 偏 向 锁 。
PS: JVM 会 利 用 CAS 操 作 (CAS(Compare and Swap),比较并交换,通过利用底层硬件平台的特性,实现原子性操作), 在 对 象 头 上 的 Mark Word 部 分 设 置 线 程
ID, 以 表 示 这 个 对 象 偏 向 于 当 前 线 程 , 所 以 并 不 涉 及 真 正 的 互 斥 锁 , 因
为 在 很 多 应 用 场 景 中 , 大 部 分 对 象 生 命 周 期 中 最 多 会 被 一 个 线 程 锁 定 ,
使 用 偏 斜 锁 可 以 降 低 无 竞 争 开 销 。
如果有另外一个线程试图锁定已经被偏移过的对象,则JVM会撤销偏移锁,然后使用轻量级锁(锁升级)

为啥说synchronized是非公平锁
非公平主要表现在获取锁的方式上,公平锁的获取方式是按照请求的时间来分配锁资源,谁先来的就先把资源给谁,非公平锁的表现方式主要体现在是按照就近原则来分配资源的, 每 当 锁 被 释 放 后 , 任 何 一 个 线 程 都 有 机 会 竞 争 到 锁 ,这 样 做 的 目 的 是 为 了 提 高 执 行 性 能 , 缺 点 是 可 能 会 产 生 线 程 饥 饿 现 象

锁 消 除 : 指 虚 拟 机 即 时 编 译 器 在 运 行 时 , 对 一 些 代 码 上 要 求 同 步 , 但 被
检 测 到 不 可 能 存 在 共 享 数 据 竞 争 的 锁 进 行 消 除 。 主 要 根 据 逃 逸 分 析 。

锁 粗 化 : 原 则 上 , 同 步 块 的 作 用 范 围 要 尽 量 小 。 但 是 如 果 一 系 列 的 连 续
操 作 都 对 同 一 个 对 象 反 复 加 锁 和 解 锁 , 甚 至 加 锁 操 作 在 循 环 体 内 , 频 繁
地 进 行 互 斥 同 步 操 作 也 会 导 致 不 必 要 的 性 能 损 耗 。
锁 粗 化 就 是 增 大 锁 的 作 用 域

乐观锁:总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
悲观锁:悲观的认为所有的资源都有可能产生并发问题,每次对数据进行操作时,在整个过程中都将数据锁定,传统关系型数据库里面的很多锁就是采用的这种机制,例如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。java里面的synchronize和ReentrantLock等重入锁就是采用的这种机制;

Synchronized 显 然 是 一 个 悲 观 锁 , 因 为 它 的 并 发 策 略 是 悲 观 的 :
不 管 是 否 会 产 生 竞 争 , 任 何 的 数 据 操 作 都 必 须 要 加 锁 、 用 户 态 核 心 态 转换 、 维 护 锁 计 数 器和 检 查 是 否 有 被 阻 塞 的 线 程 需 要 被 唤 醒 等 操 作 。随 着 硬 件 指 令 集 的 发 展 , 我 们 可 以 使 用 基 于 冲 突 检 测 的 乐 观 并 发 策 略 。先 进 行 操 作 , 如 果 没 有 其 他 线 程 征 用 数 据 , 那 操 作 就 成 功 了 ;如 果 共 享 数 据 有 征 用 , 产 生 了 冲 突 , 那 就 再 进 行 其 他 的 补 偿 措 施 。 这 种乐 观 的 并 发 策 略 的 许 多 实 现 不 需 要 线 程 挂 起 , 所 以 被 称 为 非 阻 塞 同 步

乐观锁的核心算法是:CAS操作,CAS涉及到3个值的操作,内存值、新值、预期值,如果当且仅当预期值和内存值相等时,才将内存值修改为新值,这 样 处 理 的 逻 辑 是 , 首 先 检 查 某 块 内 存 的 值 是 否 跟 之 前 我 读 取 时 的 一样 , 如 不 一 样 则 表 示 期 间 此 内 存 值 已 经 被 别 的 线 程 更 改 过 , 舍 弃 本 次 操作 , 否 则 说 明 期 间 没 有 其 他 线 程 对 此 内 存 值 操 作 , 可 以 把 新 值 设 置 给 此块 内 存CAS 具 有 原 子 性 , 它 的 原 子 性 由 CPU 硬 件 指 令 实 现 保 证 , 即 使 用JNI 调 用 Native 方 法 调 用 由 C++ 编 写 的 硬 件 级 别 指 令 , JDK 中 提供 了 Unsafe 类 执 行 这 些 操 作

所谓原子操作是指不会被线程调度机制打断的操作;这种操作一旦开始,就一直运行到结束,中间不会有任何 context switch (切换到另一个线程)
原子操作是不可分割的,在执行完毕之前不会被任何其它任务或事件中断

互斥锁:
互斥锁为资源引入一个资源锁定状态,当一个资源进入执行的时候,被锁定,其余想要获取这个资源的现场全部陷入等待,等这个资源被执行完毕,锁释放了之后,这个资源才能被其余的线程获取到

乐 观 锁 避 免 了 悲 观 锁 独 占 对 象 的 现 象 , 同 时 也 提 高 了 并 发 性 能 , 但 它 也
有 缺 点 :

  1. 乐 观 锁 只 能 保 证 一 个 共 享 变 量 的 原 子 操 作 。 如 果 多 一 个 或 几 个 变 量 , 乐
    观 锁 将 变 得 力 不 从 心 , 但 互 斥 锁 能 轻 易 解 决 , 不 管 对 象 数 量 多 少 及 对 象
    颗 粒 度 大 小 。
  2. 长 时 间 自 旋 可 能 导 致 开 销 大 。 假 如 CAS 长 时 间 不 成 功 而 一 直 自 旋 , 会
    给 CPU 带 来 很 大 的 开 销 。
  3. ABA 问 题 。 CAS 的 核 心 思 想 是 通 过 比 对 内 存 值 与 预 期 值 是 否 一 样 而 判断 内 存 值 是 否 被 改 过 , 但 这 个 判 断 逻 辑 不 严 谨 , 假 如 内 存 值 原 来 是 A,后 来 被 一 条 线 程 改 为 B, 最 后 又 被 改 成 了 A, 则 CAS 认 为 此 内 存 值 并没 有 发 生 改 变 , 但 实 际 上 是 有 被 其 他 线 程 改 过 的 , 这 种 情 况 对 依 赖 过 程值 的 情 景 的 运 算 结 果 影 响 很 大 。 解 决 的 思 路 是 引 入 版 本 号 , 每 次 变 量 更新 都 把 版 本 号 加 一 。

所有锁的最终目的是为了让所有的线程都能看到某种标记,从而让线程判断资源是否被锁定或释放
synchronized是通过在对象头设置一个标记实现找个目的,通过JVM的原生锁实现方式实现
reentrantlock和其他的所有的基于lock实现的锁都是通过一个volatile的int变量进行操作,并保证每个线程都能拥有对这个变量的可见性和原子修改,其本质就是基于AQS框架

AQS框架:
AQS( AbstractQueuedSynchronizer 类 ) 是 一 个 用 来 构 建 锁 和 同 步 器的 框 架 , 各 种 Lock 包 中 的 锁 ( 常 用 的 有 ReentrantLock、ReadWriteLock) , 以 及 其 他 如 Semaphore、 CountDownLatch, 甚至 是 早 期 的 FutureTask 等 , 都 是 基 于 AQS 来 构 建 。
AQS会在内部创建一个volatile的int类型变量state,这个变量对于所有的线程来说都是可见性和原子修改的,当有线程调用lock方法时,首先会判断state变量的值,如果state的值等于0的话,说明没有任何线程操作资源和占有共享资源的锁,那该线程就能够获得锁,并操作资源,并将state的值置为1,如果state的值等于1的情况下,说明已经有线程正在操作共享变量和资源,其余的线程必须先进入等待中。

AQS通过访问一个双向链表的结构的同步队列,来完成线程的排队工作,当有线程获取锁失败后,就会被添加到队列末尾,
NODE类是对要访问同步代码的线程的封装,其中包含了线程本身及其等待状态waitStatus(有五 种 不 同 取 值 , 分 别 表 示 是 否 被 阻 塞 , 是 否 等 待 唤 醒 ,是 否 已 经 被 取 消 等 )
每个NODE节点关联其下一个节点(next),和上一个节点(pre),方便当前线程执行完释放锁之后快速的唤醒下一个线程,这是一个FIFO(first input first output,先进先出)的过程
NODE类的两个常量,分别代表SHARED和EXCLUSIVE,分别代表共享模式和独占模式,共享模式就是一个锁资源允许同时被多个线程进行访问操作(semaphore就是基于AQS的共享模式实现的),独占模式就是,同一个时间段只允许一个线程对资源进行访问,其他的线程都要排队。

AQS 通过内部类ConditionObject构建等待队列(可有多个),当Condition调用wait()方法后 ,线 程将会加入等待队列中 ,而当Condition 调 用 signal() 方 法 后 ,线 程 将 从 等 待 队 列 转 移 动 同 步 队 列 中进 行 锁 竞 争 。

AQS 和 Condition 各 自 维 护 了 不 同 的 队 列 , 在 使 用 Lock 和Condition 的 时 候 , 其 实 就 是 两 个 队 列 的 互 相 移 动 。

synchronized和reentrantLock的异同:
ReentrantLock是Lock的实现类,是一个互斥的同步锁,从功能上说的话,reentrantLock比synchronized的同步操作更为精细,因为他可以像普通对象一样使用, 甚至实现synchronized没有的功能,如:
等待可中断锁:当线程在等待锁资源时,资源被另外的线程持有一直不释放,则会中断等待,直接返回,在处理执行时间很长的同步块中更为有效
时间锁:在指定时间范围内获取锁资源,如果超过时间还没有获取到则直接返回
可以判断是否有线程在排队获取锁资源
可以响应中断请求,在获取锁的过程中,允许被中断,如果被中断则抛出中断异常,锁资源被释放
从效率角度来说的话,由于synchronized是在JVM层面实现的,则JVM会自动进行锁的释放,而,reentrantLock则必须手动释放锁,必须将锁的释放方法unlock()放在finally()中,在JDK1.6之前的话,synchronized的性能是比较低效的,在大都数场景下是低于reentrantLock的性能的,但是在JDK1.61之后底层经过了大量的优化,在线程资源竞争不是特别激烈的情况下,synchronized是性能是远高于reentrantLock的,到那时,如果出现线程竞争十分激烈的情况下,synchronized的性能将会出现大幅下降,而reentrantLock的性能则会一直保持下去,不会降低

ReentrantLock是如何实现可重入的:
ReentrantLock内部定义了sync同步器(通过实现AQS和AOS,而AOS提供了一种互斥锁的持有方式),其实就是在每次加锁的时候,通过CAS算法,将线程对象放入到一个双向链表中,然后在每次获取锁的时候,比较一下当前链表的节点中的线程对象的ID和获取锁的请求的线程的ID是否为同一个,如果是同一个的话,则允许重入

ReadWriteLock 和 StampedLock:
虽然reentrantLock和synchronized都很简单使用,但是在行为上有一定的局限性,而且在实际的应用过程中,其实并不存在大量竞争的写操作,更多的是以并发的读操作为主,为了进一步优化并发的颗粒,Java提供了读写锁
读写锁的基本原理是多个读操作是不互斥的,当一个读操作试图锁定资源时,会进行判断,这个资源是否被写操作锁定,读锁将无法获取,必须等待对方资源获取完毕 ,这样可以保证不会读取到有争议的数据
ReadWriteLock代表了一对锁,下面是一个基于读写锁实现的数据结构 当数据量较大 ,并 发 读 多 、 并 发 写 少的时候,能够比纯同步版本凸显出优势

public class testReadWriteLock(){

    private final  Map<String,String> map = new HashMap();

    private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

    private final Lock read = rwl.readLock();
    private final Lock write = rwl.writeLock(); 

    public void readLockWay(String key){
        read.lcok();
        Systen.out.println("读操作锁定");
        try{
            String value = map.get(key);
            System.out.println("value :" + value);
        }catch (Exception e){
            System.out.println(e.getMessage());
        }finally{
            read.unLock();
        }
    }

    public void writeLockWay(String key,String value){

        write.lcok();
        System.out.println("写操作锁定")
        try{
            map.put(key,value);
        }catch (Exception e){
            System.out.println(e.getMessage());
        }finally{
            write.unlock();
        }

    }

}

读 写 锁 看 起 来 比 Synchronized 的 粒 度 似 乎 细 一 些 , 但 在 实 际 应 用中,其表现也并不尽如人意,主要还是因为相对比较大的开销。所以,JDK在后期引入了StampedLock,在提供类似读写锁的同时,还支持优化读模式。优化读基于假设,大多数情况下读操作并不会和写操作冲突,其逻辑是先试着修改,然后通过validate方法确认是否进入了写模式,如果没有进入,就成功避免了开销;如果进入,则尝试获取读锁

Java线程的同步器:
JUC 中 的 同 步 器 三 个 主 要 的 成 员 : CountDownLatch、 CyclicBarrier和 Semaphore, 通 过 它 们 可 以 方 便 地 实 现 很 多 线 程 之 间 协 作 的 功 能 。CountDownLatch 叫 倒 计 数 , 允 许 一 个 或 多 个 线 程 等 待 某 些 操 作 完成


    private ThreadLocal local = ThreadLocal.withInitial(() -> 6);

    public void getT(){
        local.get();
    }

    private CountDownLatch downLatch = new CountDownLatch(5);

    public static void main(String[] args) {

        ThreadLocalController threadLocalController = new ThreadLocalController();
        threadLocalController.begin();

    }

    public class Run implements Runnable{

        private int result;

        Run(int result){
            this.result = result;
        }

        @Override
        public void run() {

            try {

                Thread.sleep(1000 * result);
                downLatch.countDown();
                System.out.println("over " + result);
            }catch (Exception e){
                e.printStackTrace();
            }
        }

    }

    public void begin(){
        System.out.println("begin running");
        Random random = new Random(System.currentTimeMillis());

        for (int i = 0 ; i < 5 ; i ++){
            int result = random.nextInt(3) + i;
            new Thread(new Run(result)).start();
        }

        try {
            downLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("end");
    }

CyclicBarrier叫循环栅栏,它实现让一组线程等待至某个状态之后再全部同时执行,而且当所有等待线程被释放后,CyclicBarrier可以被重复使用。CyclicBarrier的典型应用场景是用来等待并发线程结束 ,CyclicBarrier的主要方法是await(),await()每被调用一次,计数便会减少1,并阻塞住当前线程。当计数减至0时,阻塞解除,所有在此CyclicBarrier上面阻塞的线程开始运行。在这之后,如果再次调用await(),计数就又会变成N-1,新一轮重新开始,这便是Cyclic的含义所在。CyclicBarrier.await()带有返回值,用来表示当前线程是第几个到达这个Barrier的线程。

 class TestCyclicBarrier{
        private CyclicBarrier cyclicBarrier = new CyclicBarrier(5);

        public void begin(){
            for (int i = 0 ; i < 5 ; i++){
                new Thread(new Student()).start();
            }
        }

        class Student implements Runnable{

            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                    System.out.println("over");
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
                System.out.println("all come");
            }
        }

    }

执行结果

over
over
over
over
over
all come
all come
all come
all come
all come

线程池相关问题:
在Java中的线程池中的线程,其实就算一个被抽象的静态内部类Worker,他基于AQS实现,放在一个HashSet<Worker>变量中,而需要执行的任务则放在成员变量任务队列中(BlockingQueue<Runnable> workQueue)这 样 , 整 个 线 程 池 实 现 的 基 本 思 想 就 是 : 从 workQueue中不断取出需要执行的任务,放在Workers 中进行处理。
我们可以通过在创建线程池的过程中给他配置参数来创建不同的线程池:

corePoolSize: 线程池的核心线程数
maximumPoolSize:线程池允许的最大线程数
keepAliveTime:超出核心线程数时的闲置线程的最大存活时间
workQueue:任务执行前保存的任务队列,保存由excute提交的Runnable方法

线程池创建完成启动之后,里面的线程Worker默认是不会随着线程池的创建启动的,只有当用户向线程池中提交了执行方法之后才会开始启动,然后线程池会进行判定,如果正在运行的线程数小于corePoolSize的话,提交的任务会立刻执行,如果大于corePoolSize的话,任务会先放入队列中,如果队列满了,而正在运行的线程数小于maximumPoolSize的话,也会立即创建临时非核心线程来执行任务,如果队列满了的话,而且运行线程数超过了maximumPoolSize的话,则会抛出异常RejectExecutionException,当一个线程完成当前的任务时,会自动从队列中获取下一个任务来执行,而,当一个线程完成任务后,队列中也没有任务可以执行了,那么,这个线程就闲置下来了,这样线程池就会根据keepAliveTime判断,如果当前正在运行的线程数超过了corePoolSize的话,则会立刻结束掉这个线程,然后线程池会自动收缩的corePoolSize的大小
下面是几种Java提供的默认实现好的线程池
SingleThreadExecutor:
这个线程池只有一个核心线程在工作,而言就是相当于一个单线程执行任务的线程池,这个线程池中的线程一旦出现执行异常导致停止,会立刻有一个新的线程来替换掉他,此线程池能够保证所有的任务按照我们提交任务的顺序进行执行

corePoolSize:  1 线程池的核心线程数
maximumPoolSize: 1线程池允许的最大线程数
keepAliveTime: 0L超出核心线程数时的闲置线程的最大存活时间
workQueue:new LinkedBlockingQueue<Runnable>()任务执行前保存的任务队列,保存由excute提交的Runnable方法,无限队列

FixedThreadPool:
这是一个固定大小的线程池,只有核心线程,每次提交任务就创建一个线程,直到到达核心线程池的大小,后续的任务全部放入队列中,线程池大小不变,如果一旦有线程出现异常而结束,立刻会有一个新的线程来替换掉他

自定义固定值 n
corePoolSize:  n 线程池的核心线程数
maximumPoolSize: n线程池允许的最大线程数
keepAliveTime: 0L超出核心线程数时的闲置线程的最大存活时间
workQueue:new LinkedBlockingQueue<Runnable>()任务执行前保存的任务队列,保存由excute提交的Runnable方法,无限队列

CachedThreadPool:
这是一种无界限的线程池,这种线程池能够拥有的核心线程数完全取决于JVM能够创建的最大线程数,如果线程池大小超过了任务执行所需的线程大小,则会判断闲置线程是否60秒内未执行任务,一旦超过60秒,则会立刻回收掉,当,任务数超过了当前线程数时,又会自动添加新的线程来执行任务。SynchronousQueue是一个缓冲区未1 的阻塞队列,缓存池通常用于一下生命周期很短的异步任务,

corePoolSize:  0 线程池的核心线程数
maximumPoolSize:Integer.MAX_VALUE线程池允许的最大线程数
keepAliveTime: 60L超出核心线程数时的闲置线程的最大存活时间
workQueue: new SynchronousQueue<Runnable>(),一个是缓冲区为1的阻塞队列 。

ScheduledThreadPool:
这是一个核心线程数固定,但是大小无限的线程池,此线程池支持定时以及周期性的执行任务的需求,创建一个周期性执行的线程,如果这个线程闲置,非核心线程池会在DEFAULT_KEEPALIVEMILLIS内进行回收

corePoolSize:  corePoolSize 线程池的核心线程数
maximumPoolSize:Integer.MAX_VALUE线程池允许的最大线程数
keepAliveTime: DEFAULT_KEEPALIVE_MILLIS超出核心线程数时的闲置线程的最大存活时间
workQueue: new DelayedWorkQueue()

线程池最常见的两种提交方式:

ExecutorService.execute(Runnable runnable); //runnable即线程执行的任务示例

ExecutorService.submit()方法返回的是Future对象。可以用isDone()来查询Future是否已经完成,当任务务完成时,它具有一个结果,可以调用get()来获取结果。也可以不用isDone()进行检查就直接调用get(),在这种情况下,get()将阻塞,直至结果准备就绪。

FutureTask futureTask = ExecutorService.submit(Runnable runnable);
FutureTask<T> futureTask = ExecutorService.submit(Runnable runnable,T result);
FutureTask<T> futureTask = ExecutorService.submit(Callable<T> runnable);

java线程中的内存模型:
Java程序中,所有的全局变量(包括静态变量,数组,实例等)都是在JVM的主内存中的,而对于方法的局部变量和形参等则是在线程的工作内存中,每一个新的线程执行任务时,都会开辟一个新的工作内存,在工作内存中又有从主内存中拷贝出来的各个全局变量的副本,线程对于变量的所有操作都是在工作内存中展开的,无法直接操作主内存,线程间也无法访问各自的工作内存中的对象和变量,需要访问的话,必须通过主内存进行操作。也就是说线程间变量值的传递必须通过主内存

volatile关键字为什么能够保证变量对于所有线程都可见:
关键字volatile是Java中提供的最轻量级的同步机制,被它定义的变量有两个特点:
1.保证变量对于所有线程的可见性,一旦有一个线程修改了这个变量的值,其他线程都能知道这个变量的值被修改了
2.禁止指令重排序优化,能够保证程序代码的执行顺序

Java 的 内 存 模 型 定 义 了 8 种 内 存 间 操 作 :
lock 和 unlock
 把 一 个 变 量 标 识 为 一 条 线 程 独 占 的 状 态 。
 把 一 个 处 于 锁 定 状 态 的 变 量 释 放 出 来 , 释 放 之 后 的 变 量 才 能 被 其 他 线 程
锁 定 。
read 和 write
 把 一 个 变 量 值 从 主 内 存 传 输 到 线 程 的 工 作 内 存 , 以 便 load。  把 store 操 作 从 工 作 内 存 得 到 的 变 量 的 值 , 放 入 主 内 存 的 变 量 中 。
load 和 store
 把 read 操 作 从 主 内 存 得 到 的 变 量 值 放 入 工 作 内 存 的 变 量 副 本 中 。
 把 工 作 内 存 的 变 量 值 传 送 到 主 内 存 , 以 便 write。
use 和 assgin
 把 工 作 内 存 变 量 值 传 递 给 执 行 引 擎 。
 将 执 行 引 擎 值 传 递 给 工 作 内 存 变 量 值 。
volatile 的 实 现 基 于 这 8 种 内 存 间 操 作 , 保 证 了 一 个 线 程 对 某 个
volatile 变 量 的 修 改 , 一 定 会 被 另 一 个 线 程 看 见 , 即 保 证 了 可 见 性

虽然volatile能够保证变量对于所有的线程的可见性,但是他并不能保证并发的安全性,因为volatile的操作在各个工作线程的内存是不一致的,,虽然每个工作线程中的volatile变量在使用前都要刷新到主内存中,但是Java中的的运算并不是原子性的,这就导致了voaltile的变量的运算在并发下也是不安全的。

volatile和synchronized的区别就在于synchronized能够保证操作的原子性

ThreadLocal和synchronized 的区别在于synchronized是利用了锁机制使得变量或代码块在某个时间段内只能被一个线程访问,是一种以时间换空间的机制,而ThreadLocal的机制则在于,给每一个线程提供了一个变量的拷贝,使得每一个线程在同一个时间内访问到的并非同一个内存对象,根除了对内存的共享,是一种以空间换时间的机制

ThreadLocal是怎么解决并发安全的:
ThreadLocal这是Java提供的一种保存线程私有信息的机制,因为其在整个线程生命周期内有效,所以可以方便地在一个线程关联的不同业务模块之间传递信息,比如事务ID、Cookie等上下文相关信息。ThreadLocal为每一个线程维护变量的副本,把共享数据的可见范围限制在同一个线程之内,其实现原理是,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本

PS:使用ThreadLocal需要注意renmove掉Worker,ThreadLocal是基于一个所谓的ThreadLocalMap,在这个map中,他的key是一个弱引用,通常弱引用都会和引用机制配合清理机制使用,但是这个map没有这么干,这就意味着,废弃的项目并不会直接被回收,他依赖于显示的回收,否则就需要等待线程结束,进而回收相应的ThreadLocalMap,这就会导致OOM问题,而且用这个最好不要和线程池一起使用,,因为Worker线程是不会自行退出的,,所以一定要记住remove。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容