《Java 虚拟机并发编程》笔记

并发

线程数 = CPU可用核心数 / ( 1 – 阻塞系数 )
阻塞系数的取值在 0 - 1 之间,计算密集型任务的阻塞系数是 0,IO 密集型任务的阻塞系数接近于 1。

构建计算密集型并发应用程序的几点经验:
* 子任务的划分数不少于处理器核心数;
* 线程数多于处理器核心数对性能提升毫无帮助;
* 在子任务划分超过一定数量之后,再增加子任务划分数对于性能的提升将十分有限。

保持一个合理的划分数,并使所有处理器核心都有足够的工作量才是关键。

应该尽可能地提供共享不可变性,否则就应该遵循隔离可变性原则,即保证总是只有一个线程访问可变变量。

开多少个线程以及如何拆分问题都会影响到你的并发应用程序的性能,还要权衡每个子任务的工作负载和划分开销。

继续阅读

Spring MVC 与 web开发

项目组用了 Spring MVC 进行开发,觉得对里面的使用方式不是很满意,就想,如果是我来搭建开发环境,我会怎么做?下面就是我的想法,只关注于 MVC 的 View 层。

一、统一的响应格式

现在基本上都是用 ajax 来调用后台接口,拿到 json格式的数据再展示,有的人直接返回数据,却没有考虑异常的情况,我觉得返回的报文里必须包含表示可能的异常信息的数据和业务响应数据。我定义了下面这个类来表示报文格式:

/**
 * 统一的 HTTP 响应格式。<br/>
 * code 为 "ok" 表示业务调用成功,否则是失败的错误码,如果有多个则以逗号分隔。<br/>
 * data 是业务数据,如果失败了则是 null。
 * 
 * @author http://coderbee.net
 *
 */
public class RespBody {
    public static final String OK_CODE = "ok";
    private final String code;
    private final Object data;

    private static final RespBody OK = new RespBody(OK_CODE, null);

    private RespBody(String code, Object data) {
        this.code = code;
        this.data = data;
    }

    public static RespBody ok() {
        return OK;
    }

    public static RespBody ok(Object data) {
        return new RespBody("ok", data);
    }

    public static RespBody error(String code) {
        return new RespBody(code, null);
    }

    public static RespBody error(String code, Object msg) {
        return new RespBody(code, msg);
    }

    public String getCode() {
        return code;
    }

    public Object getData() {
        return data;
    }
}

这个类提供了一些静态方法来快速构建响应报文,这也是很重要的一个设计:用静态工厂方法而不是构造函数。

这里的 code 不应该是直接的错误提示信息,应该只是简单的错误编码,这样不同的客户端都可以调用这个 API,然后再根据错误编码、客户端语言和自己的客户端特性选择合适的错误提示信息和提示方式。

继续阅读

《大型网站技术架构》 笔记 - 架构篇

第四章 瞬时响应:网站的高性能架构

4.1 网站性能测试

性能测试是性能优化的前提和基础,也是性能优化结果的检查和度量标准。

性能测试的指标有:响应时间、并发数、吞吐量、性能计数器。

网站性能优化的目的,除了改善用户体验的响应时间,还要尽量提升系统吞吐量,最大限度利用服务器资源。

4.2 Web 前端性能优化

主要手段有优化浏览器访问、使用反向代理、CDN加速等。

继续阅读

Java8 StampedLock

概述

StampedLock 是基于能力的锁,用三种模式来控制读/写访问。StampedLock 的状态包含了 版本和模式。锁获取方法根据锁的状态返回一个表示和控制访问的标志(stamp),“try”版本的这些方法可能返回一个特殊的值 0 来表示获取失败。锁释放和它的变换方法要求一个标志作为参数,如果它们不符合锁的状态就失败。这三种模式是:

  • 写:方法 writeLock 可能阻塞等待独占访问,返回一个标志,可用在方法 unlockWrite 以释放锁。也提供了无时间和带时间版本的 tryWriteLock 方法。当锁以写模式持有时,没有读锁可以获取,所有乐观性读确认将失败。

  • 读:方法 readLock 可能为非独占访问而阻塞等待,返回一个标志用于方法 unlockRead 以释放锁。也提供了无时间和带时间版本的 tryWriteLock 方法。

  • 乐观读:只有在锁当前没有以写模式持有时,方法 tryOptimisticRead 返回一个非 0 标志。如果锁自给定标志以来没有以写模式持有,方法 validate 返回 true 。这种模式可以认为是一种极弱版本的读锁,可以在任意时间被写者打破。在短的只读代码段使用乐观模式常常可以减少竞争和提升吞吐量。然而,它的使用天生是脆弱的。乐观读片段section应该只读字段并持有到本地变量,用于以后使用,在确认以后。乐观读模式里的字段读取可能很不一致,所以惯例只用于当你对数据表示足够熟悉,可以检查一致性和/或重复调用 validate() 方法。例如,这些步骤典型地在第一次读取对象或数组引用,然后访问其中字段、元素或方法时要求。

继续阅读

《大型网站技术架构》 笔记 - 概述篇

第一章 大型网站架构演化

1.1 大型网站软件系统的特定

  • 高并发、大流量
  • 高可用
  • 海量数据
  • 用户分布广泛,网络情况复杂
  • 安全环境恶劣
  • 需求快速变更,发布频繁
  • 渐进式发展

1.2 大型网站架构演化发展历程

应用服务与数据服务在同一台机器 –> 应用服务与数据服务分离 –> 使用缓存改善网站性能 –> 使用应用服务集群改善网站的并发处理能力 –> 数据库读写分离 –> 使用反向代理和CDN加速网站响应 –> 使用分布式文件系统和分布式数据库系统 –> 使用NoSQL和搜索引擎 –> 业务拆分 –> 分布式服务

网站使用的缓存分为:应用服务器上的本地缓存和专门的分布式缓存服务器上的远程缓存。

为了便于应用程序访问读写分离后的数据库,通常在应用服务器端使用专门的数据访问模块,使数据库读写分离对应用透明。

继续阅读

Java8 Striped64 和 LongAdder

数据 striping

根据维基百科的这段说明

In computer data storage, data striping is the technique of segmenting logically sequential data, such as a file, so that consecutive segments are stored on different physical storage devices.

Striping is useful when a processing device requests data more quickly than a single storage device can provide it. By spreading segments across multiple devices which can be accessed concurrently, total data throughput is increased. It is also a useful method for balancing I/O load across an array of disks. Striping is used across disk drives in redundant array of independent disks (RAID) storage, network interface controllers, different computers in clustered file systems and grid-oriented storage, and RAM in some systems.

数据 striping 就是把逻辑上连续的数据分为多个段,使这一序列的段存储在不同的物理设备上。通过把段分散到多个设备上可以增加访问并发性,从而提升总体的吞吐量。

Striped64

JDK 8 的 java.util.concurrent.atomic 下有一个包本地的类 Striped64 ,它持有常见表示和机制用于类支持动态 striping 到 64bit 值上。

设计思路

这个类维护一个延迟初始的、原子地更新值的表,加上额外的 “base” 字段。表的大小是 2 的幂。索引使用每线程的哈希码来masked。这个的几乎所有声明都是包私有的,通过子类直接访问。

表的条目是 Cell 类,一个填充过(通过 sun.misc.Contended )的 AtomicLong 的变体,用于减少缓存竞争。填充对于多数 Atomics 是过度杀伤的,因为它们一般不规则地分布在内存里,因此彼此间不会有太多冲突。但存在于数组的原子对象将倾向于彼此相邻地放置,因此将通常共享缓存行(对性能有巨大的副作用),在没有这个防备下。

部分地,因为Cell相对比较大,我们避免创建它们直到需要时。当没有竞争时,所有的更新都作用到 base 字段。根据第一次竞争(更新 base 的 CAS 失败),表被初始化为大小 2。表的大小根据更多的竞争加倍,直到大于或等于CPU数量的最小的 2 的幂。表的槽在它们需要之前保持空。

一个单独的自旋锁(“cellsBusy”)用于初始化和resize表,还有用新的Cell填充槽。不需要阻塞锁,当锁不可得,线程尝试其他槽(或 base)。在这些重试中,会增加竞争和减少本地性,这仍然好于其他选择。

继续阅读

Java SE 6 故障排除指南 – 4、系统崩溃故障排除

崩溃或致命错误导致进程异常终止。有各种可能的理由导致崩溃。例如,崩溃可能是由于HotSpot VM、系统库、Java SE 库或API、程序本地代码、甚至操作系统里的 bug。极端因素如操作系统资源耗尽也可以导致崩溃。

因 HotSpot VM 或 Java SE库代码导致的崩溃是罕见的。本章提供如何检查崩溃的建议。有时候可以变通崩溃直到导致崩溃的源被诊断和修复(也就是可以避开崩溃)。

通常,崩溃的第一步是定位致命错误日志。这是HotSpot VM生成的文本文件。附录C-致命错误日志 解释了如何定位文件和文件的详细描述。

4.1 崩溃样本

本节展示一些样本来说明错误日志是如何用于启发崩溃原因的。

4.1 测定哪里发生崩溃

错误日志头显示了有问题的帧。见 C.3 格式头。

如果顶层帧是本地帧且不是操作系统本地帧,这表明问题可能发生在本地库,而不是在JVM里。解决崩溃的第一步是研究本地库发生崩溃的源。有三个选择,取决于本地库的源。

如果本地库是由你的程序提供,研究你的本地库的源代码。选项 -Xcheck:jni 可以帮助查找本地 bug。见 B.2.1 -Xcheck:jni 选项。

如果你的程序使用的本地库是由其他供应商提供,报告bug,提供致命错误日志信息。

继续阅读

Java SE 6 故障排除指南 – 5、挂起或循环进程故障排除

本章为挂起或循环进程的故障排除在特定程序上提供了信息和指导。

问题在涉及挂起或循环进程时发生。挂起可能因为多种原因发生,但经常是源于程序代码、API代码或库代码里的死锁。挂起甚至是因为 HotSpot VM的bug。

有时候,一个表面上是挂起的可能是个循环。例如,VM进程里的bug导致一个或多个线程进入死循环,会消耗掉所有可得CPU周期。

诊断挂起的最初步骤是找出VM进程是空闲还是消耗了所有可得CPU周期,为做这个要求使用操作系统工具。如果进程表现为繁忙且消耗了所有可得CPU周期,那么问题很可能是循环线程而不是死锁。

继续阅读

Java SE 6 故障排除指南 – 3、内存泄露

内存泄露故障排除

如果你的应用程序执行的时间越来越长,或如果操作系统执行越来越慢,这可能是内存泄露的指示。换句话说,虚拟内存被分配但在不需要时没有归还。最终应用程序或系统没有可用内存,应用程序非正常终止。

这篇文章提供了一些涉及内存泄露的问题诊断的建议。

3.1 OutOfMemoryError 的含义

一个最常见的内存泄露的指示是 java.lang.OutOfMemoryError 错误。这个错误在Java堆或堆的特定区域没有足够空间用于分配对象时抛出。垃圾收集器不能创造更多可用空间来容纳一个新的对象,堆也不能扩展。

当 java.lang.OutOfMemoryError 错误抛出时,栈轨迹也会被打印。

java.lang.OutOfMemoryError 也可以被本地库代码抛出,当本地分配不能满足时,例如,交换空间很低。

诊断 java.lang.OutOfMemoryError 的一个早期步骤是确定错误的含义。它是否意味着Java堆满了,或意味着本地堆满了?为帮助你回答这个问题,下面的子章节解释了一些可能的错误信息,还有连接到更详细信息的链接:

继续阅读

Java SE 6 故障排除指南 – 1、诊断工具和选项

英文完整文档:http://www.oracle.com/technetwork/java/javase/tsg-vm-149989.pdf

诊断工具和选项

这章介绍了JDK 6 和 Java SE6 里不同的诊断和监视工具。这些工具在第二章详细介绍。

附录 D 列出了这个发布里的可用工具,还有上次发布以来的改变。

注意:本章描述的一些命令行工具是实验性的。例如 jstack, jinfo, jmap 就是实验性的。这些工具可能在将来的JDK发布里改变,或者不包括在将来的发布里。

继续阅读