西维蜀黍

【Java】字符(char)

背景

究竟什么是字符?

众所周知,一个字符(character)就是一个字((letter),一串字母组成一个单词,一组单词组成句子,以此类推。然而,事实上,在计算机中,在屏幕上描述的字符(被称为字符的图符),和为这个字符指定的数值(被称为代码值),并不是直接对应的。

  ...


【Java】I/O - I/O 基本操作

I/O 简介

I/O就是输入和输出,核心是I/O流,流用于读写设备上的数据,包括硬盘文件、键盘、网络…。

  ...


【Java】字符(串)编码与解码

背景

究竟什么是字符?

众所周知,一个字符(character)就是一个字((letter),一串字母组成一个单词,一组单词组成句子,以此类推。然而,事实上,在计算机中,在屏幕上描述的字符(被称为字符的图符),和为这个字符指定的数值(被称为代码值),并不是直接对应的。

  ...


【Java】多线程 - 线程间通信工具 CountDownLatch、CyclicBarrier 和 Phaser 类

本文将介绍常用的线程间通信工具 CountDownLatch、CyclicBarrier 和Phaser 的用法,并结合实例介绍它们各自的适用场景及相同点和不同点。

  ...


【Lock】锁的几种特性

我们来介绍一下与锁相关的几个概念。

  • 可重入锁(Reentrant Lock)
  • 公平锁(fair locking)和非公平锁(unfair locking)
  • 可中断锁(Interruptable Lock)
  • 可限时锁(Timed Lock)
  • 读写锁(Read-Write Lock)
  • 自旋锁(Spin Lock)
    • 适应性自旋锁(Adaptive Spinning Lock)
  • 独享锁(Exclusive Lock) VS 共享锁(Shared Lock)
  • 偏向锁(Biased Lock)
  ...


【Java】锁 - AQS

引言

在 JDK1.5 之前,一般是靠 synchronized 关键字来实现线程对共享变量的互斥访问。synchronized是在字节码上加指令,依赖于底层操作系统的Mutex Lock实现。

而从JDK1.5以后,Java界的一位大神—— Doug Lea 开发了AbstractQueuedSynchronizer(AQS)组件,使用原生Java代码实现了synchronized语义。换句话说,Doug Lea没有使用更“高级”的机器指令,也不依靠JDK编译时的特殊处理,仅用一个普普通通的类就完成了代码块的并发访问控制,比那些费力不讨好的实现不知高到哪里去了。

java.util.concurrent包有多重要无需多言,一言以蔽之,是Doug Lea大爷对天下所有Java程序员的怜悯。

AQS定义了一套多线程访问共享资源的同步器框架,是整个java.util.concurrent包的基石,Lock、ReadWriteLock、CountDowndLatch、CyclicBarrier、Semaphore、ThreadPoolExecutor等都是在AQS的基础上实现的。

CAS(Compare And Swap)

CAS 指的是现代 CPU 广泛支持的一种对内存中的共享数据进行操作的一种特殊指令。这个指令会对内存中的共享数据做原子的读写操作。简单介绍一下这个指令的操作过程:

首先,CPU 会先获取这个要修改的值的当前值,然后进行一个原子修改操作。在这个原子操作内部,会再次当这两个值相等时,CPU 才会将内存中的数值替换为新的值。否则便不做操作。最后,CPU 会将旧的数值返回。

这一系列的操作是原子的。它们虽然看似复杂,但却是 Java 5 并发机制优于原有锁机制的根本。简单来说,CAS 的含义是“我认为原有的值应该是什么,如果是,则将原有的值更新为新值,否则不做修改,并告诉我原来的值是多少”。

CAS通过调用JNI(Java Native Interface)调用实现的。JNI允许java调用其他语言,而CAS就是借助C语言来调用CPU底层指令实现的。Unsafe是CAS的核心类,它提供了硬件级别的原子操作

Doug Lea大神在java同步器中大量使用了CAS技术,鬼斧神工的实现了多线程执行的安全性。CAS不仅在AQS的实现中随处可见,也是整个java.util.concurrent包的基石。

AQS,非阻塞数据结构和原子变量类(java.util.concurrent.atomic包中的类),这些concurrent包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。从整体来看,concurrent包的实现示意图如下:

在Java中,sun.misc.Unsafe 类提供了硬件级别的原子操作来实现这个CAS。 java.util.concurrent 包下的大量类都使用了这个 Unsafe.java 类的CAS操作。

  ...


【Java】锁 - ReentrantLock 类

ReentrantLock 类

ReentrantLock 重入锁,是实现了 Lock 接口的一个类,也是在实际编程中使用频率很高的一个锁。

ReentrantLock 具有以下特点:

  • 支持重入性(reentrancy),表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。
  • 支持**公平锁(fair locking)非公平锁(unfair locking)**两种方式。
  • 是**可中断(Interruptible)**锁,即是一个可以被中断持有的锁。
  • 是**可限时锁(Timed Lock)**锁,可以传入一个超时值作为参数。当在到达超时时间前,仍然没有获得锁,则放弃对锁的持有申请。
  ...


【Java】多线程 - Java 锁的演化

锁存在的问题

Java 在 JDK1.5 之前都是靠 synchronized 关键字保证同步的,这种通过使用一致的锁定协议来协调对共享状态的访问,可以确保无论哪个线程持有共享变量的锁,都采用独占的方式来访问这些变量。独占锁(exclusive locking)其实就是一种悲观锁(pessimistic locking),所以说 synchronized 是一种悲观锁。

  ...


【Java】多线程-Java 保证原子性、有序性、可见性

线程安全(Thread Safety)

概念

当一个可变(mutable)对象会被多个线程访问时,就要考虑这个线程是不是需要被设计成线程安全的。

一个类会被称为**线程安全(thread-safe)**的,当它被从多个线程访问,而且无论这些线程如何被调度(scheduling)和交叉(interleaving)执行,而且在调用代码(calling code)中不需要额外的同步(synchronization)或者其他协调(coordiantion)机制,它的行为仍然正确(若预期执行)。

换句话说,线程安全的类封装(encapsulate)了已经需要的同步机制,因此客户端或者说调用者不再需要关注或者提供这些同步机制。

  ...


【Java】多线程 - 原子类(Atomic Classes)

背景

我们知道 volatile 关键字虽然轻量级,但不能保证原子性,synchronized 可以保证原子性,但是比较重量级。

那么有没有一种简单的、性能高的方法来保证 Java 的原子操作呢?答案当然是有的。

在 JDK1.5 时期, Java 家族加入了 Atomic 包。

原子类(Atomic Classes)

Java 中提供了对应的原子操作类来实现原子地且有条件进行的读-改-写操作操作。其本质是利用了 CPU 级别的原子指令。由于原子类是基于 CPU 级别的指令,因此其开销比需要操作系统参与的锁的开销小。

CAS 算法

CAS (比较与交换,Compare and swap) 算法是一种非阻塞算法(non-blocking algorhithm),同时也是一种无锁算法(lock-free algorhithm),基于乐观并发(pessimistic concurrent control)的思想。

非阻塞版本相对于基于锁的版本有几个性能优势。首先,它用硬件的原生形态代替 JVM 的锁定代码路径,从而在更细的粒度层次上(独立的内存位置)进行同步,失败的线程也可以立即重试,而不会被挂起后重新调度。更细的粒度降低了争用的机会,不用重新调度就能重试的能力也降低了争用的成本。即使有少量失败的 CAS 操作,这种方法仍然会比由于锁争用造成的重新调度快得多。

以 Atomic 类中的 incrementAndGet() 方法为例,其内部就调用了Unsafe中的 native 方法(CompareAndSet)以实现递增数值:

private volatile int value;
public final int get() {
        return value;
    }
/**
 * Atomically increments by one the current value.
 *
 * @return the updated value
 */
public final int incrementAndGet() {
    for (;;) {
        int current = get();
        int next = current + 1;
        if (compareAndSet(current, next))
            return next;
    }
}

我们来分析下incrementAndGet的逻辑:

  1. 先获取当前的value值
  2. 对value加一
  3. 第三步是关键步骤,调用compareAndSet方法来进行一个原子更新操作,这个方法的语义是:先检查当前value是否等于current,如果相等,则意味着value没被其他线程修改过,更新并返回true。如果不相等,compareAndSet则会返回false,然后循环继续尝试更新。
  ...