• Java并发编程实战


    1.简介

      1.1并发简史

        产生原因:资源利用率、公平性、便利性

      1.2线程的优势

        1.2.1发挥多处理器的强大功能

        1.2.2建模的简单性

        1.2.3异步事件的简化处理

        1.2.4响应更灵敏的用户界面

      1.3线程带来的风行

        1.3.1安全性问题

          永远不发生糟糕的事情

        1.3.2活跃性问题

          某件正确的事情最终会发生

        1.3.3性能问题

          希望正确的事情尽快发生

      1.4线程无处不在

        所使用的框架可能会创建线程

        Timer类,用于延迟运行任务一次或多次,确保访问对象是线程安全的

        Servlet和JSP,需要保证多个Servlet共享对象的线程安全

        远程方法调用

        Swing和AWT

    2.线程安全性

      2.1什么是线程安全性

        多个线程访问某个类时,这个类始终都能表现出正确的行为,这个类是线程安全的

        无状态对象一定是线程安全的

      2.2原子性

        2.2.1竞态条件

          当计算的正确性取决于线程的执行时序时,就会发生竟态条件

          基于失效的观察结果做出判断的竟态条件称为“先检查后执行”

        2.2.2示例:延迟初始化中的竞态条件

        2.2.3复合操作

          将“先检查后执行”和“读取-修改-写入”等操作统称为复合操作

      2.3加锁机制

        2.3.1内置锁

        2.3.2重入

      2.4用锁来保护状态

      2.5活跃性与性能

        当执行时间较长的计算或者可能无法快速完成的操作时,一定不要持有锁

    3.对象的共享

      3.1可见性

        3.1.1失效数据

          获取到一个未更新的值

        3.1.2非原子的64位操作

          获取的失效数据是之前的值而非随机值是最低安全性,但不适用于非volaties类型的64位数值变量,因为JVM会把64位的读操作和写操作分解为2个32位的操作

          在多线程中使用共享且可变的long和double等类型的变量是不安全的,除非用volatie声明或锁保护

        3.1.3加锁与可见性

        3.1.4Volatile变量

          Volatile声明的变Volatile变量量是共享的,读取Volatile变量总能获取最新的值

          但是Volatile变量比较脆弱,仅当Volatile变量能简化代码的失效以及对同步策略的验证时才应该使用它们

          加锁机制可以确保可见性和原子性,Volatile变量只能确保可见性

      3.2发布与逸出

        发布:使对象能够在当前作用域之外的代码中使用

        逸出:不应该被发布的对象被发布时称为逸出

        安全的对象构造过程

          不要在构造过程使用this逸出

          使用工厂来放在this引用在构造过程中逸出

      3.3线程封闭

        3.3.1Ad-hoc线程封闭

          维护线程封闭性的职责完全由程序实现,但是具有脆弱性,尽量少用

        3.3.2栈封闭

        3.3.3ThreadLocal类

          使线程中的值与保存值的对象关联起来,总能获取到最新值

          ThreadLocal变量类似于全局变量,降低了代码的可重用性,在类之间引入了隐含的耦合性

      3.4不变性

        3.4.1Final域

        3.4.2示例:使用Volatile类型来发布不可变对象

      3.5安全发布

        3.5.1不正确的发布:正确的对象被破坏

        3.5.2不口不对象与初始化安全性

        3.5.3安全发布的常用模式

          在静态初始化函数中初始化一个对象引用

          将对象的引用保存到volatile类型的域或者AtomicReferance对象中

          将对象的引用保存到某个正确构造对象的final类型域中

          将对象的引用保存到一个由锁保护的域中

        3.5.4事实不可变对象

          对象在技术上看是可变,但是其状态在发布后不会再改变的对象称为“事实不可变对象”

        3.5.5可变对象

          不可变对象可以任意发布

          事实不可变对象通过安全方式发布

          可变对象通过安全方式发布,并且必须是线程安全的或者锁保护起来

        3.5.6安全的共享对象

          策略:线程封闭、只读共享、线程安全共享、保护对象 

    4.对象的组合

      4.1设计线程安全的类

        4.1.1收集同步需求

        4.1.2依赖状态的操作

        4.1.3状态的所有权

      4.2实例封闭

        4.2.1Java监视器模式

        4.2.2实例:车辆追踪

      4.3线程安全性的委托

        4.3.1示例:基于委托的车辆追踪器

        4.3.2独立的状态变量

        4.3.3当委托失效时

        4.3.4发布底层的状态变量

        4.3.5示例:发布状态的车辆追踪器

      4.4在现有的线程安全类中添加功能

        4.4.1客户端加锁机制

          对使用某个对象X的客户端代码,使用X本身用于保护其状态的锁来保护这段客户端代码

          若没有则添加的正确的辅助类

    public class ListHelper<E> {
        public List<E> list = Collections.synchronizedList(new ArrayList<>());
    
        public boolean putIfAbsent(E x) {
            synchronized (list) {//注意加锁的对象
                boolean absent = !list.contains(x);
                if (absent) {
                    list.add(x);
                }
                return absent;
            }
        }
    }

          会破坏同步策略的封装性

        4.4.2组合

    public class ImprovedList<T> implements List<T> {
        private final List<T> list;
    
        public ImprovedList(List<T> list) {
            this.list = list;
        }
    
        public synchronized boolean putIfAbsent(T x) {
            boolean contains = list.contains(x);
            if (contains) {
                list.add(x);
            }
            return !contains;
        }
    }

      4.5将同步策略文档化

        解释含糊的文档

    5.基础构建模块

      5.1同步容器类

        5.1.1同步容器类的问题

        5.1.2迭代器与Concurrent-ModificationException

        5.1.3隐藏迭代器

      5.2并发容器

        5.2.1ConcurrentHashMap

        5.2.2额外的原子Map操作

        5.2.3CopyOnWriteArrayList

          当迭代操作远远多于修改操作时才应该使用“写入时复制”容器

      5.3阻塞队列和生产者-消费模式

        5.3.1示例:桌面搜索

        5.3.2串行线程封闭

        5.3.3双端队列与工作密取

      5.4阻塞方法与中断方法

        恢复中断

      5.5同步工具类

        5.5.1闭锁

          延迟线程的进度直到其到达终止状态,用于确保某些活动直到其他活动都完成才执行

    public class TestHarness {
        public long timeTasks(int nThrads, final Runnable task) throws InterruptedException {
            final CountDownLatch startGate = new CountDownLatch(1);//起始门
            final CountDownLatch endGate = new CountDownLatch(nThrads);//结束门
            for (int i = 0; i < nThrads; i++) {
                Thread t = new Thread() {
                    public void run() {
                        try {
                            startGate.await();//等待所有线程准备就绪
                            try {
                                task.run();
                            } finally {
                                endGate.countDown();//完成一件事情就减1
                            }
                        } catch (InterruptedException ignored) {
    
                        }
                    }
                };
                t.start();
            }
            long start = System.nanoTime();
            startGate.countDown();
            endGate.await();
            long end = System.nanoTime();
            return end - start;
        }
    }

        5.5.2FutureTask

          用于闭锁

        5.5.3信号量

          计数信号量用于控制同时访问特定资源的操作数量

        5.5.4栅栏

          所有线程都必须同时到达栅栏才能继续执行,栅栏可以重置复用

      5.6构建高效且可伸缩的结果缓存

    6.任务执行

      6.1在线程中执行任务

        6.1.1串行地执行任务

        6.1.2显示地为任务创建线程

        6.1.3无限制创建线程的不足

          线程生命周期的开销非常高

          资源消耗大

          稳定性差

      6.2Executor框架

        6.2.1示例:基于Executor的Web服务器

    public class Test {
        private static final int NTHREADS = 100;
        private static final Executor exec = Executors.newFixedThreadPool(NTHREADS);
    
        public static void main(String[] args) throws IOException {
            ServerSocket socket = new ServerSocket(8080);
            while (true) {
                final Socket connection = socket.accept();
                Runnable task = new Runnable() {
                    public void run() {
                        handleRequest(connection);
                    }
                };
                exec.execute(task);
            }
        }
    }

        6.2.2执行策略

        6.2.3线程池

          newFixedThreadPool()创建固定长度的线程池

          newCachedThreadPool()创建可缓存的线程池,当前规模超过处理需求时,回收空闲线程,需求增加时,添加新线程,规模大小无限制

          newScheduledThreadPool()以延迟或定时方式执行任务的固定线程池

        6.2.4Executor的生命周期

        6.2.5延迟任务与周期任务

          Timer类精准性有问题,而且抛出异常就会被取消

          newScheduledThreadPool是基于相对时间的,用于间隔时间的执行的任务,例如1分钟检查一次活动

      6.3找出可利用的并行性

        6.3.1示例:串行的页面渲染器

        6.3.2携带结果的任务Callable与Future

        6.3.3示例使用Future实现页面渲染器

        6.3.4在异构任务并行化中存在的局限

        6.3.5CompletionService:Executor与BlockingQueue

        6.3.6示例:使用CompletionService实现页面渲染器

        6.3.7为任务设置时限

        6.3.8示例:旅行预定门户网站

          invokeAll()可以将按照任务集合中迭代器的顺序将所有Future添加到返回的集合中,当所有任务执行完毕或中断或超时,invokeAll将返回

    7.取消与关闭

      7.1任务取消

        7.1.1中断

          用户请求取消

          有时间限制的操作,搜索任务超时,取消正在搜索的任务

          应用程序事件,解决方法找到时,取消其他搜索解决方案的任务

          错误

          关闭,程序或服务器关闭

          发出中断请求,线程在合适的时刻中断自己

        7.1.2中断策略  

        7.1.3响应中断

        7.1.4示例:计时运行

        7.1.5通过Future来实现取消

        7.1.6处理不可中断的阻塞

          Java.io包中的同步Socket I/O

          Java.io包中的同步I/O

          Selector的异步I/O

          获取某个锁

        7.1.7才有newTaskFor来封装非标准的取消

      7.2停止基于线程的服务

        7.2.1示例:日志服务

        7.2.2关闭ExecutorService

        7.2.3“毒丸”对象

          当队列得到这个“毒丸”对象,立即停止

        7.2.4示例:只执行一次的服务

        boolean checkMail(Set<String> hosts, long timeout, TimeUnit unit) throws InterruptedException {
            ExecutorService exec = Executors.newCachedThreadPool();
            final AtomicBoolean hasNewMail = new AtomicBoolean((false));
            try {
                for (final String host : hosts) {
                    exec.execute(new Runnable() {
                        public void run() {
                            if (checkMail(host)) {
                                hasNewMail.set(true);
                            }
                        }
                    });
                }
            } finally {
                exec.shutdown();//安全的关闭
                exec.awaitTermination(timeout, unit);
            }
            return hasNewMail.get();
        }

        7.2.5shuadownNow的局限性

      7.3处理非正常的线程终止

        未捕获一次的处理

      7.4JVM关闭

        7.4.1关闭钩子

          通过Runtime.addShutdownHook注册的但尚未开始的线程

          用于实现服务或应用程序的清理工作

        7.4.2守护线程

          主线程创建的所有线程都是普通线程

          主线程创建之外的所有线程都是守护线程

        7.4.3终结器

    8.线程池的使用

      8.1在任务与执行处理之间的耦合

        8.1.1线程饥饿死锁

          一个线程无期限的等待其他线程的资源或条件会发送线程饥饿死锁

        8.1.2运行时间较长的任务

      8.2设置线程池的大小

      8.3配置ThreadPoolExecutor

        8.3.1线程的创建与销毁

        8.3.2管理队列任务

        8.3.3饱和策略

          “中止”策略,抛弃下一个将要被执行的任务

          “调用者运行”策略,将任务回退给调用者

        8.3.4线程工厂

        8.3.5在调用构造函数后再定制ThreadPoolExecutor

      8.4扩展ThreadPoolExecutor

        示例:给线程池添加统计信息

      8.5递归算法的并行化

        示例:谜题框架

    9.图形用户界面应用程序

      9.1为什么GUI是多线程的

        9.1.1串行事件处理

        9.1.2Swing中的线程封闭机制

      9.2短时间的GUI任务

      9.3长时间的GUI任务

        9.3.1取消

        9.3.2进度标识和完成标识

        9.3.3SwingWorker

      9.4共享数据模型

        9.4.1线程安全的数据模型

        9.4.2分解数据模型

      9.5其他形式的单线程子系统

    10.避免活跃性危险

      10.1死锁

        10.1.1锁顺序死锁

        10.1.2动态的锁顺序死锁

        10.1.3在协作对象之间发送的死锁

        10.1.4开放调用

          调用方法时不持有锁,这种调用称为开放调用

        10.1.5资源死锁

      10.2死锁的避免与诊断

        10.2.1支持定时的锁

        10.2.2通过线程转储信息来分析死锁

      10.3其他活跃性危险

        10.3.1饥饿

          要避免使用线程优先级,会增加平台依赖性

        10.3.2糟糕的响应性

        10.3.3活锁

          多个相互协作的线程对彼此进行响应从而修改各自的状态,使得任何一个线程都无法继续执行时,就发生了活锁

          通过等待随机长度的时间和回退可以有效的避免活锁的发生

    11.性能与可伸缩性

      11.1对性能的思考

        11.1.1性能与可伸缩性

          运行速度指标:服务器时间、等待时间

          处理能力指标:生产量、吞吐量

          可伸缩性:增加计算资源时,程序的吞吐量或者处理能力相应地增加

        11.1.2评估各种性能权衡因素

      11.2Amdahl定律

        11.2.1示例:在各种框架中隐藏的串行部分

        11.2.2Amdahl定律的应用

          准确估计出执行过程中串行部分所占的比例

      11.3线程引入的开销

        11.3.1上下文切换

          可运行的线程大于CPU数量时,保存当前运行线程的执行上下文,新调度进来的线程执行上下文设置为当前上下文

        11.3.2内存同步

        11.3.3阻塞

      11.4减少锁的竞争

        11.4.1缩小锁的范围("快进快出")

        11.4.2减小锁的粒度

        11.4.3锁分段

        11.4.4避免热点域

        11.4.5一些替代独占锁的方法

        11.4.6监测CPU的利用率

          负载不充足

          I/O密集

          外部限制

          锁竞争

        11.4.7向对象池说“不”

      11.5示例:比较Map的性能

      11.6减少上下文切换的开销

    12.并发程序的测试

      12.1正确性测试

        12.1.1基本的单元测试

        12.1.2对阻塞操作的测试

        12.1.3安全性测试

        12.1.4资源管理的测试

        12.1.5使用回调

        12.1.6产生更多的交替操作

      12.2性能测试

        12.2.1在PutTakeTest增加计时功能

        12.2.2多种算法的比较

        12.2.3响应性衡量

      12.3避免性能测试的陷阱

        12.3.1垃圾回收

        12.3.2动态编译

        12.3.3对代码路径的不真实采样

        12.3.4不真实的竞争程度

        12.3.5无用代码的清除

      12.4其他的测试方法

        12.4.1代码审查

        12.4.2静态分析工具

        12.4.3面向方面的测试技术

        12.4.4分析与监测工具

    13.显示锁

      13.1Lock与ReentrantLock

        13.1.1轮询锁与定时锁

        13.1.2可中断的锁获取操作

        13.1.3非块结构的加锁

      13.2性能考虑因素

      13.3公平性

      13.4在synchronized和ReentrantLock之间进行宣传

      13.5读-写锁

    14.构建自定义的同步工具

      14.1状态依赖性的管理

        14.1.1示例:将前提条件的失败传递给调用者

        14.1.2示例:通过轮询与休眠来实现简单的阻塞

        14.1.3条件队列

      14.2使用条件队列

        14.2.1条件谓词

        14.2.2过早唤醒

        14.2.3丢失的信号

        14.2.4通知

        14.2.5示例:阀门类

        14.2.6子类的安全问题

        14.2.7封装条件队列

        14.2.8入口协议与出口协议

      14.3显示的Condition对象

      14.4Synchronized剖析

      14.5AbstractQueuedSynchronizer

      14.6java.util.concurrent同步器类的AQS

        14.6.1ReentrantLock

        14.6.2Semaphore与CountDownLatch

        14.6.3FutureTask

        14.6.4ReentrantReadWriteLock

    15.原子变量与非阻塞同步机制

      15.1锁的劣势

      15.2硬件对并发的支持

        15.2.1比较并交换

        15.2.2非阻塞的计数器

        15.2.3JVM对CAS的支持

      15.3原子变量类

        15.3.1原子变量是一种更好的volatile

        15.3.2性能比较:锁与原子变量

      15.4非阻塞算法

        15.4.1非阻塞的栈

        15.4.2非阻塞的链表

        15.4.3原子的域更新器

        15.4.4ABA问题

    16.Java内存模型

      16.1什么是内存模型,为什么需要它

        16.1.1平台的内存模型

        16.1.2重排序

        16.1.3Java内存模型简介

        16.1.4借助同步

      16.2发布

        16.2.1不安全的发布

        16.2.2安全的发布

        16.2.3安全初始化模式

        16.2.4双重检查加锁

      16.3初始化过程中的安全性

  • 相关阅读:
    Node.js server render Blob file All In One
    JavaScript Composite Design Patterns All In One
    cloudflare CDN image 403 HTTP error All In One
    CSS grid layout gap & flex layout gap All In One
    设置npm的registry
    正则验证身份证、数字、数字字母
    Markdown 基本语法
    Java小知识01
    计算机小知识
    丢失的数字 三角形最大周长
  • 原文地址:https://www.cnblogs.com/plxz/p/9530500.html
Copyright © 2020-2023  润新知