《Thinking in Java 》读书笔记-1

第一章 对象导论

1.1 抽象过程

  • 万物皆对象。
  • 程序是对象的集合,它们通过发送消息来告知彼此索要做的。
  • 每个对象都有自己的由其他对象所构成的储存。
  • 每个对象都拥有其类型。
  • 某个特定类型的所有对象都可以接受同样的消息。

1.2 每个对象都要有一个接口

1.3 每个对象都提供服务

1.4 被隐藏的具体实现

1.5 复用具体实现

1.6 继承

1.6.1 “is-a”和”like-a”

1.7 伴随多态的可互换对象

1.8 单根继承结构

1.9 容器

1.10 对象的创建和生命期

1.11 异常处理:处理错误

1.12 并发编程

1.13 Java与Internet

总结

  • 过程式编程:
     数据定义和函数调用
  • 面向对象编程:
     用来表示问题空间概念的对象(而不是有关计算机表示方式的相关内容),以及发送给这些对象的用来表示在此空间内的行为的消息。

《Netty实战》-第五章-ByteBuf

《Netty实战》-第五章-ByteBuf


5.1 ByteBuf的API

Netty的数据处理API通过两个组件暴露—abstract class ByteBuf和interface ByteBufHolder。
下面是一些ByteBuf API的优点:

  • 他可以被用户自定义的缓冲区类型扩展
  • 通过内置的复合缓冲区类型实现了透明的零拷贝
  • 容量可以按需增长(类似于JDK的StringBuilder)
  • 在读和写这两种模式之间切换不需要调用ByteBuffer的flip()方式
  • 读和写使用不同了的索引
  • 支持方法的链式调用
  • 支持引用计数
  • 支持池化

其他类可用于管理ByteBuf实例的分配,以及执行各种针对数据容器本身和它所持有的数据的操作。


5.2 ByteBuf类 —— Netty的数据容器

5.2.1 它是如何工作的

ByteBuf维护两个不同的索引:一个用于读取,一个用于写入。

名称以read或者write开头的ByteBuf方法,将会推进其对应的索引,而名称以set或者get开头的操作则不会。后面的这些方法将一个参数传入的一个相对索引上执行操作。

5.2.2 ByteBuf的使用模式

  1. 堆缓冲区
    最常用的ByteBuf模式是将数据储存在JVM的堆空间中。这种模式被成为支撑数组。
  2. 直接缓冲区
    直接缓冲区是另一种ButeBuf模式,在垃圾回收之外。
    3.复合缓冲区
    复合缓冲区为多个ByteBuf提供了一个聚合视图,

5.3 字节级操作

5.3.1 随机访问索引

BuyteBuf的索引是从零开始的:第一个字节的索引是0,最后一个字节的索引总是capacity()-1

5.3.2 顺序访问索引

ByteBuf同时具有读索引和写索引,但是JDK的ByteBuffer却只有一个索引,这也是为甚恶魔必须调用filp()方法来在读模式和写模式之间进行切换的原因。

5.3.3 可丢弃字节

5.3.4 可读字节

ByteBuf的可读字节分段储存例如实际数据。新分配的、包管理的或者复制的缓冲区的默认的readerIndex的值为0。

5.3.5 可写字节

可写字节分段是指一个拥有未定义内容的、写入就绪的内存区域。新分配的缓冲区的writerIndex的默认值为0。

5.3.6 索引管理

5.3.7 查找管理

调用clear()比调用discardReadBytes()轻量得多,因为它将只是重置索引而不会复制任何得内存。

5.3.7 查找操作

indexOf()方法可以用来确定指定值得索引的方法。

5.3.8 派生缓冲区

派生缓冲区为ByteBuf提供了专门的方式来呈现其内容的视图。这类视图是通过一下方法被创建的:

  • duplicate()
  • slice()
  • slice(int, int)
  • Unpoooled.unmodifiableBuffer { … }
  • order(ByteOrder)
  • readSlice(int)

每个这些方法都将返回一个新的ByteBuf实例,它具有自己的读索引、写索引和标记索引。

BYteBuf复制 如果需要一个现有缓冲区的真实副本,请使用copy()或者copy(int, int)方法。不同于派生缓冲区,由于这个调用所返回的ByteBuf拥有独立的数据副本。

5.3.9 读/写操作

netty有两种类别的读/写操作。

  • get()和set()操作,从给定的索引开始,并且保持索引不变。
  • read()和write()操作,从给定的索引开始,并且会根据已经访问过的字节数对索引进行掉整。

5.3.10 更多的操作

ByteBufHolder接口

ByteBufHolder有几种用于访问底层数据和引用计算的方法。

5.5 ByteBuf分配

5.5.1 按需分配:ByteBufAllocator接口

为了降低分配和释放内存的开销,Netty通过interface ByteBufAllocator实现了(ByteBuf的)池化,它可以用来分配我们所描述过的任意类型的ByteBuf实例。使用池化是应用程序的决定,其并不会以任何方式改变ByteBuf API(的语义)。

Netty提供了两种ByteBufAllocator的实现:PooledByteBufAllocatorUnpooledByteBufAllocator。前者池化了ByteBuf的实例以提高性能并最大限度地减少内存碎片.此实现使用了一种为jemalloc的已被大量现代操作系统所采用的高效方法来分配内存。后者的实现不池化ByteBuf实例,并且在每次被它调用时都会返回一个新的实例。

5.5.2 Unpooled缓冲区

Unpooled的工具类提供了静态的辅助方法来创建未池化的ByteBuf实例。

5.5.3 ByteBufUtil类

ByteBufUtil.hexdump()方法以十六进制的表示形式打印bytebuf的内容。

5.6 引用计算

引用计算是一种通过在某个对象所持有的资源不再被其他对象引用时释放该对象所持有的资源来优化内存使用和性能的技术。ByteBuf和BytebufHolder引入了引用计数计数,它们都实现了interface ReferenceCounted。

5.7 小结

ByteBuf的要点:

  • 使用不同的读索引和写索引来控制数据访问。
  • 使用内存的不同方式——基于字节数组和直接缓冲区
  • 通过CompositeByteBuf生成多个ByteBuf的聚合视图
  • 数据访问方法——搜索、切片以及复制
  • 读、写、获取和设置API
  • ByteBufAllocator池化和引用计算

《Java并发编程实战》-9

第10章 避免活跃性危险


10.1 死锁

哲学家问题

10.1.1 锁顺序死锁

如果所有线程以固定的顺序来获得锁,那么在线程中就不会出现锁顺序死锁问题。

10.1.2 动态的顺序死锁

通过锁顺序来避免死锁

10.1.3 在协助对象之间发生的死锁

如果在持有锁时调用某个外部方法,那么将出现活跃性问题。在这个外部方法中可能会获取其他锁(这可能会产生死锁),或者阻塞时间过长,导致其他线程无法及时获得当前被持有的锁。

10.1.4 开放调用

如果在调用某个方法时不需要持有锁,那么这种调用被称为开放调用(Open Call)。

在程序中应尽量使用开发调用。与那些在持有锁时调用外部方法的程序相比,更易于对依赖于开放调用的程序进行死锁分析。

10.1.5 资源死锁

当多个线程早相同的资源集合上等待时,也会发生死锁。

10.2 死锁的避免与诊断

10.2.1 支持定时的锁

显式使用Lock类中定时tryLock功能代替内置锁机制可以检测死锁和从死锁中恢复过来。

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

虽然防止死锁的主要责任在于你自己,但JVM仍然通过线程转储(Thread Dump)来帮助识别死锁的发生。线程转储包括各个运行中的线程的栈追踪信息,这类似发生异常时的栈追踪信息。

10.3 其他活跃性危险

活跃性危险:

  • 死锁
  • 饥饿
  • 丢失信号
  • 活锁

10.3.1 饥饿

当线程由于无法访问它所需要的资源而无法继续执行时,就发生了“饥饿”,引发饥饿的最常见资源就是CPU时钟周期。

要避免使用线程优先级,因为这会增加平台依赖性,并可能导致活跃性问题。在大多数并发应用程序中,都可以使用默认的线程优先级。

10.3.2 糟糕的响应性

10.3.3 活锁

活锁是另一种形式的活跃性问题,该问题尽管不会阻塞线程,但也不能继续执行,因为线程将不断重复地执行相同的操作,并且总会失败。活锁通常发生在处理事务消息的应用程序中:如果蹦年成功处理某个消息,那么消息处理机制将回滚整个事务,并且将它重新发到队列的开头。

总结

活跃性故障时一个非常严重的问题,因为当出现活跃性故障时,除了终止应用程序之外没有其他任何机制可以邦帮助从这些故障中恢复过来。最常见的活跃性故障就是所顺序死锁。在设计时应该避免产生锁顺序死锁:确保线程在获取多个锁时采用一致的顺序。最好的解决方法是在程序中始终使用开放调用。这种大大减少需要同时持有多个锁的地方,也更容易发现这些地方。