美文网首页
(并发三)线程安全框架,结构化并发程序

(并发三)线程安全框架,结构化并发程序

作者: _____Mori_ | 来源:发表于2018-08-11 12:31 被阅读0次

    前言:前两篇读书笔记记录了书本提及的线程安全基本理论概念,同时结合博文和实际的案例进行描述。接下来记录的是线程安全性封装的相关知识。这几章就像索引一样给个全貌(流水账读书笔记 ,没太多可看性),具体知识点细节比如某个并发类源码实现原理或者死锁各种锁的详解需要后续章节抽出来剖析。

    一、组合对象

    1.设计线程安全的类:
      比起发布状态到公有静态域,将状态封装起来将更易于线程安全性的管理。(如果发布的话请参考上一篇,需要在本类外部进行安全性处理,或者考虑安全发布等比较复杂or局限的操作。)
      设计三要素
       1.1. 找出所有的状态变量:状态自身是否可见安全
       1.2. 找出约束状态变量的不变性:多个状态之间的不变性语义,状态约束,后验条件,先验条件
       1.3. 建立状态变量的并发访问管理策略:管理共享变量
    2.线程封闭:


       2.1线程封闭形式之一,监视器模式:对象外包装构建一个安全类,包装类持有该状态对象的唯一引用,保证客户每次调用访问都对该对象上锁。很多线程安全的类都是基于这个模式去实现的。( 源码举例Collections.synchronizedCollection(new ArrayList());)
    对象包装上锁,持有唯一引用,封闭线程
        static class SynchronizedCollection<E> implements Collection<E>, Serializable {
            private static final long serialVersionUID = 3053995032091335093L;
    
            final Collection<E> c;  // Backing Collection
    ...
    ...
    省略代码
    ..
     SynchronizedCollection(Collection<E> c) {
                this.c = Objects.requireNonNull(c);
                mutex = this;
            }
    
            SynchronizedCollection(Collection<E> c, Object mutex) {
                this.c = Objects.requireNonNull(c);
                this.mutex = Objects.requireNonNull(mutex);
            }
    
            public int size() {
                synchronized (mutex) {return c.size();}
            }
            public boolean isEmpty() {
                synchronized (mutex) {return c.isEmpty();}
            }
            public boolean contains(Object o) {
                synchronized (mutex) {return c.contains(o);}
            }
    

       2.2:deepcopy,类似Vector源码那样,对可变对象(不安全对象)进行拷贝并赋值到本对象的final成员中,虽然可以实现线程安全,但是影响性能。而且可能出现近期快照问题。


    书中举例车辆位置追踪代码
    1. 线程安全性委托:上述内容是从头组装构建一个线程安全类,考虑监视器。但是假设类中大部分组件都是线程安全,那么委托考虑一下。部分情况可以这么做:



      但是假设各个成员状态之间存在某些逻辑上的不变性关系,比如A成员限制要小于B,就要考虑复合操作的线程安全性了。

    2. 在现有安全类中加功能:继承或封装咯,比如看图


    3. 同步策略文档化:略

    二、基础构建模块

    JAVA自带类库提供了丰富的并发基础构建模块,这一章主要是讲这些

    1. 同步容器:
        之前其实也提过Vector这类早期安全容器。包括Collections.sychronizedXxx等工厂方法创建的类,基本都是封装线程状态,并对公有方法同步。
         1.1. 同步容器类虽然自身线程安全,但是某些情况需要客户端额外加锁。说到烂的复合操作以及语义定义的不变性条件。
         1.2.迭代器与ConcurrentModificationException :一般采用迭代器可以解决单线程下的容器遍历和修改,但是即使是迭代器,遇到并发迭代操作,它们也是及时抛出上述Exception去及时失败。→这可能要求我们迭代的时候给容器上锁(可能有死锁),或者使用克隆(克隆这一步也要上锁)容器。

         1.3. 大概了解有这么回事。System或者Log输出String的时候
    2. 并发容器:ConcurrentHashMap,CopyOnWriteArrayList这些,在遍历操作中代替同步List。也加了一些复合操作。但是问题:不能保证size,isEmpty这些精确。毕竟并发环境下这些计算结果可能失效,只是个近似值。强化的是get,put,containsKey,remove这些。
         2.1. ConcurrentHashMap 无论是看博客还是源码都大概知道是分段锁的思想,这里先概括而过把。后续的章节好像会细讲
         2.2. public class ConcurrentHashMap<K,V> extends AbstractMap<K,V>

      implements ConcurrentMap<K,V>, Serializable
         2.3. CopyOnWriteArrayList 副本保证线程安全,只有当迭代多于操作,才考虑这个。(比如用于事件通知系统,大部分是遍历操作找到监听对象然后调用,而少数才是对监听对象的注册或者注销操作)
    3. 阻塞队列、生产者消费者模式:拆分需要完成的工作以及执行工作。要记录的就这一点把



      补充一个双端队列和工作密取:


    4. 阻塞方法和中断方法:大多数情况只需要传递InterruptedException或者手动调用当前线程的interrupt()。用于阻塞等待线程的额外耗时操作结果比如IO。

    5. 同步工具类:

         5.1. 闭锁:CountDownLatch,使多个线程等待一组事件发生。包含计数器,计数器非0会一直阻塞等待到为0,或者超时或者中断。
      FuctureTask,熟悉线程池和Callable那套API的同学应该不陌生,Fucture对象的get方法调用会阻塞等待子线程执行结果,回调返回自己定义的结果。确保了结果的安全发布。
         5.3. 信号量 Semaphore:
      (mark:不可重入锁,自旋锁,方法级别,如果线程两个方法嵌套调用,或者递归,自旋锁会锁死。可重入锁,线程级别。可重入就意味着:线程可以进入任何一个它已经拥有的锁所同步着的代码块。)
         5.4. 栅栏,类似闭锁。但是CyclicBarrier需要同时到达后执行,闭锁则是等待。比如一家人要一起到齐才算打开栅栏,可以执行下一步比如吃饭。闭锁是来了就吃,count-1。 这个用的不熟。 mark
    1. 构建并发缓存示例:PDF99 忘了就单独去看看 图和码说的很清晰 一步步优化(内置锁处理计算→去除内置锁换用并发ConcurrentMap→有重复计算问题又把Map的V换成Fucture<V>,一旦发现某个线程计算完了就返回future对象→但后验问题解决不来→换用putIfAbsent)
    三、任务执行
    1. 线程中执行任务
         1.1.串行执行任务,一旦遇到耗时任务,CPU空闲,任务阻塞其他任务。
         1.2.并行执行任务,每个任务创建一个线程。
         1.3.显示创建线程执行每个任务的不足:线程有生命周期,创建线程好资源。如果请求过多,可能造成资源不足服务器崩溃。
    2. Executor框架:基于生产者消费者模型,子线程任务相当于消费者
         2.1. 略,简单的使用示例
         2.2. 执行策略:



         2.3. 线程池: 持有线程资源,可重用,提高响应。



         2.4. 生命周期:运行,关闭(不允许提交),已停止(任务完成后)。→优雅关闭线程池 shutdown(拒收)然后awaitTermination(定时终止,避免整体超时)最后finally里检验isTermination,如果还没关闭就调用shutdownNow尝试中断正在执行的任务。
         2.5. 延时任务:Timer由于单线程,任务之间可能由于耗时影响定时精确性。建议用任务调度线程池。
    3. 找出可利用的并行性
         3.1.举例页面渲染(文字+图片)的串行性劣势。读图片耗时
         3.2.介绍Callable和Future
         3.3.利用Future去优化页面渲染示例
         3.4. 相比同构任务的并行,异构任务并行存在局限。一个渲染文字一个渲染图片,渲染文字很快完成,渲染图片阻塞。那么其实对性能提高也不算高。
         3.5. CompletionService:Executor和阻塞队列介绍
         3.6.利用上述CompletionService实现,一旦有图片渲染完成的子线程就入队读取显示。而不是阻塞future的get等所有都渲染完成。
         3.7. future.get可以设置时限,比如获取广告超时执行默认操作推送默认广告。
    四、取消与关闭
    1. 任务取消:操作过程中任务能被外部代码置为完成,称之为可取消的。




         1.1. 中断:用thread.sleep、thread.join、thread.wait、JDK1.5condition.await等操作都可进入阻塞状态,而线程在检查中断标示时如果发现中断标示为true,则会在这些阻塞方法调用处抛出InterruptedException异常。
         1.2. 中断策略:检查到中断时应该做退出,重试或者清理等操作。由执行语义决定。任务中断,线程中断都需要相应应对策略。提高程序健壮性和响应性
         1.3.响应中断:向上抛出异常或者处理之后恢复中断状态(再次Thread.interupt)确保调用链路的中断传递无误。
         1.4. 计时运行错误示范。没有响应中断。超时后当然正常,任务还没运行就可以中断。超时前完成,然后还执行中断代码。



         1.5. 用future得了
         1.6. 不可中断的阻塞,要知道阻塞原因,除了中断,还要手动处理阻塞原因,比如关闭socket等。

         1.7. 不是很理解对非标准取消的封装。

    2. 停止基于线程的服务:比如A线程创建B线程,B任务执行周期比A存活长,就要管理下B任务的生命周期。
         2.1.书本拿消费者生产者日志模型举例,一步步从最原始的阻塞,线程安全,一步步优化。PDF140
      托管给ExecutorService的几个关闭API
         2.3.毒丸对象
         2.4.示例 略
    3. 处理非正常线程停止:日志监测,RuntimeException处理
    4. JVM关闭:。。。。
      其实整章绕来绕去就是说Executor和Future带来多么便利。
    五、线程池的使用:

    1.任务间的隐形耦合:在Executor框架将任务的提交和执行解耦的前提下,我们修改和制定执行策略会更方便灵活。但并非所有的任务都适用所有的执行策略。有些任务就需要明确指定执行策略:①依赖性任务。②使用线程封闭机制的任务,任务隐形耦合③对响应时间敏感的④使用ThreadLocal的任务
       1.1线程饥饿死锁:如果任务依赖,一个任务将另一个任务提交到同一个线程工作队列并等待这个任务的结果,第二个任务又在等待第一个任务完成才可以继续。那么就会产生饥饿死锁。比如单线程Executor的页面渲染任务:本任务需要渲染页头和脚,本任务占有一个线程坑位,等下个任务完成。下个任务又阻塞在线程池里
       1.2 等待时间过长的任务:阻塞过久,不出现活跃性问题的话其性能也不佳。
    2.设置线程池大小:P154自己看 公式。。。用得着这么精确吗=-=
    3.配置ThreadPoolExecutor:挑选适合的实现。比如FIFO啊还是缓冲啊有界无界是否定时啊。
       3.1——3.5:P168PDF Executor详解 另外抽一章


    END

    流水账读书笔记完成。看过一遍,记笔记再过一遍,这样印象还是蛮深的。还差100页这本书就OK了√

    相关文章

      网友评论

          本文标题:(并发三)线程安全框架,结构化并发程序

          本文链接:https://www.haomeiwen.com/subject/ephwvftx.html