java并发编程读书摘要

一、基础概念了解

    1、上下文切换:CPU通过时间片来循环执行任务,时间片用完会切换到下一个线程,但是会保存当前状态,任务从保存到再

加载的过程就是一次上下文切换。

    2、减少上下文切换的方式:

      无锁并发编程:避免竞争锁造成的上下文切换

      CAS算法:Compare and Swap,核心方法compareAndSetInt

      使用最少线程:避免创建不必要的线程

      使用协程:在单线程里实现多任务的调用,并在单线程里维护多个任务间的切换

   3、死锁:资源竞争时,互相等待对方释放锁才能继续执行但又都不释放锁时造成死锁

      避免死锁常见方法:

      (1)避免一个线程同时获取多个锁

      (2)避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源

      (3)尝试使用定时锁,Lock.tryTime(timeout)来代替使用内部锁机制

      (4)对于数据库锁,加锁和解锁必须在同一个数据库连接里

二、java并发机制

    1、volatile:轻量级的synchronized,保证共享变量在多线程间的可见性

      优化:LinkedTransferQueue

      缓存行:内存缓存的最小单位,目前主流处理器缓存行占64位字节,Object占4个字节,所以一般追加字节到64字节

    2、synchronized原理和应用

      实现同步的基础:java中的每一个对象都可以作为锁,具体表现为普通同步方法(锁当前示例)、静态同步方法(锁当前类的

Class对象)、同步方法块(锁配置的对象)

      实现原理:JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,但实现方式不一样。代码块同步是使用

monitorenter和monitorexit指令实现

    3、锁的升级与对比:在1.6中锁一共有4种状态,从低到高依次是:无锁状态——>偏向锁状态——>轻量级锁状态——>重量

级锁状态;锁可随竞争情况升级但不能降级

  (1)偏向锁:每次获取与释放锁都会浪费很多资源,很多情况下竞争锁不是由多个线程而是由一个线程在访问,因此会在对象

头中记录当前线程的pid等信息,下次进来时会检查是否包含当前线程信息。

  (2)轻量级锁:JVM会创建用于锁记录的信息,将放在对象头的MarkWord复制到锁记录。开始竞争锁的时候,当竞争成功的

时候只有一个线程获取这个轻量级锁,其他线程一直自旋等待,如果自旋获取锁失败则会发生锁膨胀升级成重量级锁,轻量级锁

由于另一个线程的竞争锁会导致CAS替换Mark Word失败从而释放锁并唤醒等待的线程

  (3)重量级锁:其它线程尝试获取锁时都会被阻塞,当持有锁的线程释放锁之后会唤醒阻塞线程,然后进入新一轮的锁竞争

   锁对比如下表所示:
优点缺点适用场景
偏向锁加锁和解锁不需额外消耗存在锁竞争时会存在额外的锁撤销消耗适用只有一个线程访问同步块场景
轻量级锁竞争的线程不会阻塞,提高了响应速度得不到锁竞争的线程,自旋会消耗CPU追求响应时间,同步块执行速度快
重量级锁线程竞争不使用自旋,不消耗CPU线程阻塞,响应时间缓慢追求吞吐量,响应时间较长

4、原子操作的实现原理

  (1)使用总线锁:利用处理器的Lock#信号,当一个处理器输出此信号时阻塞其它处理器的请求

  (2)使用缓存锁:缓存一致性机制

5、java实现原子操作

  (1)循环CAS实现:例如Atomic类的CompareAndSet(expect, update)

 private void safeCount() {
        for (;;) {
            int i = atomicI.get();
            if (atomicI.compareAndSet(i, ++i)) {break;}
        }
    }

  (2)CAS实现原子操作的3大问题:ABA问题、循环时间长开销大、只能保证一个共享变量的原子操作

  (3)使用锁机制实现原子操作:偏向锁、轻量级锁和互斥锁等,除偏向锁之外其它锁实现都采用了循环CAS方式

三、java内存模型(JMM)

    1、线程通信(共享内存和消息传递)和线程同步(控制不同线程间操作发生相对顺序的机制)

    2、内存模型的抽象结构

       实例域、静态域和数组都存放于堆内存中,堆内存线程共享;局部变量,方法定义参数和异常处理参数不共享,即不存在内

存可见性问题。 java内存模型的抽象结构示意图可自行查看资料。

    3、从源代码到指令序列的重排序

      源代码——>编译器优化重排序——>指令级并排重排序——>内存系统重排序——>最终执行的指令序列

  重排序是指编译器为了优化程序性能而对指令序列进行重新排序的一种手段

    4、volatile的内存语义:可见性和原子性(对单个变量操作的读写具有原子性)

      写:写操作会将本地内存中的共享变量写入到主内存

      读:JMM将该线程对应的本地内存置为无效,接下来从主内存读取变量,然后更新本地内存变量

      重排序规则:写之前操作不能重排序到写之后;读之后的操作不能重排序到读之前;读不能重排序到写之前

是否能重排序第二个操作  
第一个操作普通读/写volatile读volatile写
普通读/写  No
volatile读NO    NONO
volatile写 NONO
5、锁的内存语义:略

6、concurrent包

  java线程间的通信方式:

  (1)A线程写Volatile变量,然后B线程读这个Volatile变量

  (2)A线程写volatile变量,然后B线程用CAS更新这个volatile变量

  (3)A线程用CAS更新一个volatile变量,然后B线程用CAS更新这个volatile变量

  (4)A线城用CAS更新一个volatile变量,然后B线程读这个volatile变量

  concurrent包的通用化的实现模式:

  (1)声明共享变量为volatile类型,然后利用CAS的原子条件更新来实现线程之间的同步

  (2)配合以volatile读/写和CAS所具有的volatile读/写的内存语义来实现线程之间的通信

7、双重检查锁定与延迟初始化

  (1)双重检查锁定由来,例如下面示例代码

public class UnsafeLazyInitilization {
    private static Instance instance;
    public static Instance getInstance() {
         if (null == instance) { //线程A执行
           instance = new Instance(); //线程B执行
         }
         return instance;
    }
   }

 在线程B未初始化完成时,线程A读到的实例为null,依然会去执行初始化;一种保证安全的做法加上synchronized,但是会带

来性能开销,被多个线程频繁调用时,会导致程序执行性能的下降。

  (2)因而引申出双重检查锁定(Double-checked locking):

if (null == instance) { //1
    synchronized (LazyInitilization.class) {
          if (null == instance) {
             instance = new Instance(); //2 出现问题的根源
          }
    }
}

 代码执行到1时读取到instance不为null时,instance引用的对象可能还没有完成初始化。2可以分解为以下代码:

   memory = allocate(); //1:分配对象的内存空间

   ctorInstance(memory); //2:初始化对象

   instance = memory; //3:设置instance指向刚分配的内存地址

   2和3可能会发生重排序,就是导致读取到实例不为null但是初始化未完成情况;只要2执行是在初始访问对象之前,即使2和3

重排序,单线程内执行结果也不会发生改变。(见线程执行时序图)

  (3)解决方案:

       #1、将instance声明为volatile类型(jdk5或以上版本可行),本质上是禁止2和3重排序

       #2、基于类初始化的解决方案:允许重排序,但非构造线程(线程B)看不到这个重排序

public class InstanceFactory {
    private static class InstanceHolder {
         public static Instance instance = new Instance();
    }
    public static Instance getInstance() {
         return InstanceHolder.instance ; //这里将导致InstanceHolder类被初始化
    }
}

8、JMM的内存可见性保证

  (1)单线程程序

  (2)正确同步的多线程程序:具有顺性一致性

  (3)未同步/未正确同步的多线程程序。JMM为它们提供了最小安全性保障:线程执行时读取到的值,要么是之前某个线程写

入的值,要么是默认值(0、null、false)

四、java并发编程基础(参见实战java高并发程序设计)

五、java中的锁

1、Lock接口:与synchronized相比可以显式的获取锁和释放锁

Lock lock = new ReentrantLock();
lock.lock();
try {
    ...
} finally {
    lock.unLock();
} 

特性:

  1)尝试非阻塞的获取锁:如果这一时刻锁没有被其它线程获取到,则成功获取并持有锁

  2)能被中断地获取锁:获取到锁的线程能响应中断,发生中断时抛出异常并释放锁

  3)超时获取锁:在规定时间内未获取到锁,则返回

2、队列同步器AQS(AbstractQueuedSynchronizer)

  解释:用来构建锁或其它同步组件的基础框架,使用一个int类型的state成员变量表示同步状态,内置FIFO队列完成资源获取线

程的排队工作。

  使用:主要使用方式是继承,主要抽象方法getState()、setState(int newState)和compareAndSetState(int expect,int

update)用来改变同步状态

  主要实现:ReentrantLock、ReentrantReadWriteLock和CountDownLatch等

  接口与示例:同步器的设计是基于模板模式的,使用者需继承并重写指定的方法,随后将同步器组合在自定义同步组件的实现

中,并调用提供的模板方法而模板方法会调用重写的方法

  如下通过一个独占锁示例来了解同步器的工作原理:

/**
 * 自定义同步组件,同一时刻只允许一个线程占有锁
 *
 * @author fc on 2018/4/24.
 */
public class MutexAQS implements Lock {

    /**
     * 静态内部类,继承同步器并实现独占式获取和释放同步状态
     */
    private static class Sync extends AbstractQueuedSynchronizer {
        /**
         * 是否处于占用状态:1-占用;0-未占用
         *
         * @return
         */
        protected boolean isHeldExclusively() {
            return getState() == 1;
        }

        /**
         * 当状态为0时获取锁
         *
         * @param acquires
         * @return
         */
        @Override
        public boolean tryAcquire(int acquires) {
            if (compareAndSetState(0, 1)) {
                setExclusiveOwnerThread(Thread.currentThread());
                return true;
            }
            return false;
        }

        /**
         * 释放锁并将状态置为0
         *
         * @param releases
         * @return
         */
        @Override
        protected boolean tryRelease(int releases) {
            if (getState() == 0) {
                throw new IllegalMonitorStateException();
            }
            setExclusiveOwnerThread(null);
            setState(0);
            return true;
        }

        /**
         * 返回一个condition
         *
         * @return
         */
        Condition newCondition() {
            return new ConditionObject();
        }
    }

    /**
     * 将操作代理到sync
     */
    private static final Sync SYNC = new Sync();

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

    @Override
    public void lockInterruptibly() throws InterruptedException {
        SYNC.acquireInterruptibly(1);
    }

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

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return SYNC.tryAcquireNanos(1, unit.toNanos(time));
    }

    @Override
    public void unlock() {
        SYNC.release(1);
    }

    @Override
    public Condition newCondition() {
        return SYNC.newCondition();
    }

    public boolean isLocked() {
        return SYNC.isHeldExclusively();
    }

    public boolean hasQueuedThreads() {
        return SYNC.hasQueuedThreads();
    }
}

3、重入锁

  公平性与非公平性:公平性针对获取锁而言,如果锁是公平的,则锁的获取顺序符合请求的绝对时间顺序,也就是FIFO

4、读写锁ReentrantReadWriteLock:读写分离,适合读多写少场景

5、LockSupport:构建同步组件的基础工具,可阻塞或唤醒线程,主要公共静态方法如下:

  void park()    阻塞当前线程,除非调用unpark或线程中断才能从park方法返回

  void parkNanos(long nanos)    阻塞当前线程,最长不超过nanos纳秒

  void unpark(Thread thread)    唤醒处于阻塞状态的线程Thread

  park(Object blocker)        blocker是用来标识当前线程在等待的对象,即阻塞对象,主要用来问题排查和系统监控

  parkNanos(Object blocker,long nanos)   

6、condition:提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式

Object的监视方法与condition接口的对比
对比项Object Monitor MethodsCondition
前置条件获取对象的锁Lock.lock()获取锁,Lock.newCondition()获取condition对象
调用方式直接调用如object.wait()直接调用如condition.await()
等待队列个数1个多个
当前线程释放锁并进行等待状态支持支持
等待状态中不响应中断不支持支持
释放锁并进入超时等待支持支持
释放锁并进入等待状态到将来的某个时间不支持

支持

唤醒等待队列中的一个或全部线程支持支持

    使用示例如下:

public class BoundedQueue<T> {

    private Object[] items;

    private int addIndex, removeIndex, size;

    private Lock lock = new ReentrantLock();

    private Condition notEmpty = lock.newCondition();

    private Condition notFull = lock.newCondition();

    public BoundedQueue(int capacity) {
        items = new Object[capacity];
    }

    /**
     * 添加元素
     *
     * @param t
     * @throws InterruptedException
     */
    public void add(T t) throws InterruptedException {
        lock.lock();
        try {
            //循环代替if是为了防止过早的意外或通知
            while (size == items.length) {
                //添加线程进入等待
                notFull.await();
            }
            items[addIndex] = t;
            if (++addIndex == items.length) {
                addIndex = 0;
            }
            ++size;
            System.out.println("addIndex = " + addIndex + "; size = " + size + "; add T = " + t);
            //删除线程唤醒
            notEmpty.signal();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 删除元素
     *
     * @return
     * @throws InterruptedException
     */
    public T remove() throws InterruptedException {
        lock.lock();
        try {
            while (0 == size) {
                //元素为空时,删除线程进入等待
                notEmpty.await();
            }
            Object e = items[removeIndex];
            if (++removeIndex == items.length) {
                removeIndex = 0;
            }
            --size;
            System.out.println("removeIndex = " + removeIndex + "; size = " + size + "; remove T = " + e);
            //添加线程唤醒
            notFull.signal();
            return (T)e;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        BoundedQueue<String> boundedQueue = new BoundedQueue<>(10);
        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                try {
                    boundedQueue.add(String.valueOf(i));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(() -> {
            for (int i = 0; i < 20; i++) {
                try {
                    boundedQueue.remove();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

  实现:核心为FIFO的等待队列,包含首节点和尾节点,进入等待时将当前线程构造成节点加入尾节点,然后释放锁进入等待,

当调用signal唤醒时,等待时间最长即首节点移到同步队列并将节点中线程用LockSupport唤醒

六、java并发容器和框架

  1、concurrentHashMap的实现原理与使用

      #1、结构:segment数组(是一种可重入锁,数组+链表结构,扮演锁的角色)+hashEntry数组(存储k-v数据);CHM包

含一个Segment数组,每个Segment数组包含一个HashEntry数组,当对HashEntry数组数据修改时,必须先获得对应的

Segment的锁

      #2、初始化:initCapacity是初始化容量,默认16,要求是2的N次方。loadFactory是每个segment的负载因子,根据这两

个参数来初始化每个segment

      #3、定位Segment:CHM使用锁分段segment来保护不同的数据,因此进行数据的插入和获取时必须先通过散列定位算法

定位到segment。进行再散列的目的是减少散列冲突,使元素能够均匀地分布在不同的Segment上,从而提高容器的存取效率,

否则所有元素分布在同一个segment中,那存取效率将大大降低,分段锁也失去意义。

      #4、get/put操作:get操作不需加锁;put操作会先定位到segment然后对其加锁,会先判断hashEntry是否需要扩容然后

计算位置放入数组;  扩容:插入元素前判断hashEntry容量是否达到阈值,如果达到则进行扩容,与hashMap不同的是

hashMap先插入再判断是否需要扩容,有可能导致无效扩容。扩容时先创建2倍容量的数组,然后将数组内元素再散列后插入到

新数组,CHM只会对当前segment进行扩容

      #5、size()操作:求和segment数组所有元素和,CounterCell中定义Volatile变量value保存元素个数,并通过注解保证每个

value占一个缓存行,读取到的最新数据

  2、ConcurrentLinkedQueue:是一个基于链接节点的无界线程安全队列,它采用先FIFO的规则对节点进行排序,当我们添加一

个元素的时候,它会添加到队列的尾部;当我们获取一个元素时,它会返回队列头部的元素。它采用了“wait-free”算法(即

CAS算法)来实现

  3、阻塞队列:是一个支持两个附加操作的队列,即阻塞的插入和阻塞和移除

      #1、ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列,FIFO规则,可通过构造来指定公平或非公平访问队列

      #2、LinkedBlockingQueue:一个由链表结构组成的有界阻塞队列。

      #3、PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。

      #4、DelayQueue:一个支持延迟获取元素的无界阻塞队列。使用场景如定时任务调度

      #5、SynchronousQueue:一个不存储元素的阻塞队列。

      #6、LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。

      #7、LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列

七:并发工具类

  1、countDownLatch:线程计数器

  2、CyclicBarrier:同步屏障

  3、Semaphore:控制并发线程数的信号量,见SemaphoreDemo

public class SemaphoreDemo {

    private static final int MAX_THREAD = 30;

    /**
     * 线程池最大允许30个线程同时执行
     */
    private static final ExecutorService service = Executors.newFixedThreadPool(MAX_THREAD);

    public static void main(String[] args) {
        //控制同时最多10个线程执行
        Semaphore semaphore = new Semaphore(10);
        for (int i = 0; i < MAX_THREAD; i++) {
            service.execute(() -> {
                try {
                    semaphore.acquire();
                    System.out.println("work : " + Thread.currentThread().getId() + " at time : "
                        + System.currentTimeMillis());
                    semaphore.release();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            });
        }
        service.shutdown();
    }
}

八、java中的线程池

  https://blog.csdn.net/congge_1993/article/details/73497439

九、Executor框架

  1、两级调度模型:在上层,java多线程程序通常将应用分解为若干个任务,然后使用用户级调度框架Executors将这些任务映

射为固定数量的线程。在底层,操作系统内核将这些线程映射到硬件处理器上

  2、结构:主要由3部分构成

    #1、任务:包括被执行任务需要实现的接口,如runnable和callable

    #2、任务的执行:包括任务执行的核心接口Executor,以及继承自它的ExecutorService接口

    #3、异步计算的结果:包括Future接口类和实现Future接口的FutureTask类

  3、使用:

    #1、创建Runnable或Callable接口的任务对象,或者Excutors工具类的callable方法封装一个Callable对象

    #2、直接交给ExecutorService执行(execute方法)或者提交执行(submit方法),submit将返回实现了Future接口的对象

  4、Executor框架成员,见图


  5、FutureTask:(略)后续在具体探讨
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值