码出高效读书笔记(下)

2020/03/05

第5 章 异常与日志

无论采用哪种方式处理异常 都严禁捕获 常后什么都不做或打印 行日志了事。如果在方法内部处理异常 需要 据不同的 务场景进 处理 如重试、

回滚等操作。如果向上抛出异常,如上例 需要在异常对象中添加上下文参数、

部变量、运行环境等信息,这样有利于排查问题。

异常分类

所有异常都是 Throwable 的子类,分为Error 致命异常)和 Exception (非致命异常)。

Eception 又分checked 异常(受检异常)和 unchecked 异常(非受检异常)。

finally是在return 表达式执行之后执行的,此时将要 turn 的结果 经被暂

存起来 finally 代码块执行结束后再将之前暂存的结果返回

Lock ThreadLocal InputStream等这些需要进行强制释放和清除的对象都得在

finally 代码块中进行显式的清理 避免产生内存泄漏,或者资源消耗。

异常的抛与接

推荐对外的开放接口使用错误码 ;公司内部跨应用 服务调用优先使用Result对象 封装错误码 和 错误信息;

而内部则推荐直接抛出异常对象。

可以返回null 但一定要说明什么情况 会产生null

日志

记录日志主要三个原因:

记录操作轨迹 监控系统运行状态 回溯系统故障

  • access.log 记录用户操作频度 和 跳转链接 有助于分析用户的后续行为。

  • 全面有效的日志系统有助于建立完善的应用监控体系,由此工程师可以实时监控

系统运行状况,及时预警,避免故障发生。监控系统运行状况,是指对服务器使用状

态,如内存、 CPU 等使用情况,应用运行情况 如响应时间 QPS 等交互状态;应

用错误信息,如空指针、 SQL 异常等的监控。例如,在 CPU 使用率大于 60 四核

服务器中 load 大于4时发出报警,提醒工程师及时处理,避免发生故障。

  • 当系统发生线上问题时 完整的现场日志有助于工程师快速定位问题。

日志规范

推荐的日志文件命名方式为

appName_logType logName.log 其中 logType日志类型,推荐分类有 stats monitor visit logNam 为日志描述。

综合考虑日志的保存时间至少保存15天 根据重要程度 文件大小 磁盘空间 自行延长保存时间。

曰志级别

DEBUG:记录对调试程序帮助的信息

INFO:记录程序运行的信息

WARN:表明此处有可能存在错误的可能

ERRORR:程序错误信息,没有引起系统继续运行

FAIAL:将会导致程序中断

避免无效的日志打印

日志配置文件中设置 additivity=false ,

区别对待错误日志

一些异常能够重试恢复的,例如入参错误,记为WARN这种情况是为了还原现场

如果ERROR级别就需要人为介入。

保持记录内容的完整性

1.记录异常时 需要记录 异常堆栈

2.输出有对象 要确保重写了toString方法

注意:

日志并不是越多越好

记录日志时要考虑:

1.日志是否有人看 2.看到日志做什么 3.能不能提升排查任务的效率

日志框架

日志门面

采用门面设计模式

提供一套接口规范,自身不负责日志功能的实现

目前最常用的slf4j commons-logging

日志库

log4j logback (实现了slf4j接口)

日志适配器
日志门面适配器

应为slf4j是最后提出的,所以工程中要想使用slf4j+log4j 适配器 解决不兼容问题

为什么要使用slf4j

log4j

LOGGER.info("This is a test message: " + message);

字符串相加是一个比较消耗性能的操作,字符串是一个不可变对象,一旦创建就不能被修改,创建的字符串会保存在String池中,占用内存

        if (LOGGER.isInfoEnabled()) {
            LOGGER.info("This is a test message: " + message);
        }

但是这样的代码太繁琐

slf4j

LOGGER.info("This is a test message: {}", message);

第6 章 数据结构与集合

集合:作为数据结构的载体

高并发场下集合的问题:

多线程共享集合出现的脏数据问题

集合在数据扩容出现的节点之间的死链问题

写多读少误用某些集合导致性能下降问题

二分查找法时间复杂度

二分查找在最坏的情况下依次是n/2,n/4,n/8。。。。 一直到1为止

然后我们可以观察到分母是每次都乘以1/2,分子不变,所以可以根据题意列出下面等式:

n(1/2)x = 1

得出x=logn

image-20200623001530539

第7 章 并发与多线程

image-20200621123520511

阻塞:

同步阻塞: 所被其它线程占用

主动阻塞:调用Thread的某些方法,主动让出cpu执行权,比如sleep() join()

等待阻塞:执行了wait

保证线程的安全,从以下四个维度考量

1.数据单线程内可见 最典型的是线程局部变量,他存储在虚拟机栈帧的局部变量表中,与其它线程无关。 ThreadLocal就是采用这种方法。

2.只读对象 只读对象总是安全的。它的特点是允许复制、拒绝写入。最典型的是String,Integer. 一个对象要想拒绝写入,必须满足以下条件:

  • final 修饰类避免被继承

  • private final 修饰关键字 避免修改

  • 没有更新方法

  • 返回值不能可变对象为引用

3.线程安全类

例如:StringBuffer 使用synchronized来修饰相关的方法

4.同步和锁机制

线程安全的核心概念:要么只读,要么加锁

Java并发包 java.util.concurrent JUC 主要分为以下几个类族

  • 线程同步类

    这些类使线程的协调更加容易 逐步淘汰了Object的wait和notify方式。

    主要代表为CountDownLatch Semaphore CyclicBarrier

  • 并发集合类

    ConcurrentHashMap 由最初初的锁分段 到后来的CAS不断地提高性能

    其他还有 ConcurrentSkipListMap CopyOnWriteArrayList

    BlockingQueue 等。

  • 线程管理类

    提供了多种创建线程池的创建方式

    如使用 Executors 静态工厂或者使用 ThreadPoolExecutor 等。另外,通过

    ScheduledExecutorService 来执行定时任务。

  • 锁相关类

  • 以Lock接口为核心 最有名的使ReentrantLock

计算级单线程时代没有锁,自从出现了资源竞争,才需要加锁

计算机锁 由开始的悲观锁,发展到后来的乐观锁 偏向锁 分段锁

1.并发包中的锁

2.利用同步代码块

Lock

是JUC的顶级接口,他为用到synchronized 而是利用volitate的可见性

自旋锁

自旋锁 锁之后 循环等待 ,提交效率(其它 锁是等待 等待系统调用) 所以锁的片段时间不能太长

AQS 定义了一个volatile int state 变量作为共享资源 如果获取资源失败,则进入同步FIFP队列中等待

获取成功 执行完 会通知同步队列中的线程 出对并执行(模板模式)

偏向锁

偏向锁可以 降低无竞争开销,它不是互斥锁,不存在线程竞争的情况,省去了再次判断的步骤,提高了性能。

线程同步

当一个线程对内存进行操作的时候,其它线程不可以对这个内存地址进行操作,直到该线程完成操作。

实现线程的方法有很多:比如同步方法、阻塞队列

volatile

保证共享变量的内存可见性,防止指令重排序。

轻量级的同步方式,这种说法是错误的。

适合一写多读的场景

应用:

CopyOnWriteArrayList 它在修改数据的时候会把整个集合数据复制出来,对写操作加锁

信号量同步

信号量同步 是指 在不同的线程之间,通过传递同步信号量 来协调线程的执行的先后顺序

CountDownLatch Semaphore

Post Directory