Java并发编程之AQS

什么是 AQS

同步工具类 也叫同步器(Synchronizer)。在使用同步器时,我们发现不同的同步器存在许多共同点,例如 ReentrantLock 和 Semaphore 都支持每次允许一定数量线程通过/等待/取消,也都支持让等待线程执行公平或非公平的队列操作等。

事实上,很多同步工具类在实现时都使用了共同的基类,这就是 AbstractQueuedSynchronizer(AQS),抽象队列同步器

阅读更多

Java并发编程之对象共享

对象数据共享

要实现多个线程之间的数据共享,需要考虑两个问题:

  • 通信:通信是指消息在两条线程之间传递
  • 同步:既然要传递消息,那 接收线程发送线程 之间必须要有个先后关系。此时就需要用到同步,即控制多条线程之间的执行次序。
阅读更多

Effective Java(九)异常

Item 69 异常只用于异常

异常只用于异常的情况,不要用 try-catch 捕获 ArrayIndexOutOfBoundsException 并且不做任何处理这种方式来跳出数组遍历。为什么不用 for-each 循环呢?

设计良好的 API 不应该强迫它的客户端为了正常的控制流程而使用异常。

阅读更多

Effective Java(八)General Programming

Item 57 最小化局部变量的作用域

好的编程习惯:在首次使用的地方声明它。

  1. 如果循环终止后不需要循环变量的内容,那么优先选择 for 循环而不是 while 循环。
  2. 如果变量需要在 try-catch 之外使用,那就必须在外面提前声明,这是一个例外。其他情况都应该遵循在首次使用的地方声明。
  3. 每个行为对应一个方法。保持方法小而集中。
阅读更多

Effective Java(七)方法

Item 49 检查参数有效性

在 Java 7 之后,可以用 requireNonNull 来判空,如果为空,自动抛出空指针异常。

1
this.strategy = Objects.requireNonNull(strategy, "strategy");
阅读更多

Effective Java(六)Lambdas and Streams

Item 42 lambda 表达式优于匿名类

Java 的 Lambda 表达式本质上就是一个匿名类。而什么是匿名类?就是在使用的时候现场 new 并实现的类。

只有一个方法的接口称为 函数式接口(functioning interface),Lambda 表达式本质上就是对这样子的接口做现场实现。可以参考我之前写的:Java简明笔记(八)Lambda和函数式编程

然而 lambda 也不是万能的,它只对函数是接口有用,如果一个接口有多个方法需要重写,那只能用匿名类。this 关键字在 lambda 中引用封闭实例,在匿名类中引用匿名类实例。如果你需要从其内部访问函数对象,则必须使用匿名类。

Lambdas 与匿名类都无法可靠地序列化和反序列化。因此,尽量少去 (如果有的话) 序列化一个 lambda (或一个匿名类实例)。如果有一个想要进行序列化的函数对象,比如一个 Comparator,那么使用一个私有静态嵌套类的实例(见 Item 24 )。

作者建议:一行代码对于 lambda 说是理想的,三行代码是合理的最大值。 如果违反这一规定,可能会严重损害程序的可读性。

阅读更多

Effective Java(五)枚举和注解

Item 34 使用枚举类型替代整型常量

如果你需要一组常量,比如球的红绿蓝三种颜色,四则运算的加减乘除操作,用枚举类会比用 final static intString 好。枚举更具可读性,更安全,更强大。

阅读更多

Effective Java(四)泛型

Java 5 加入了泛型。在有泛型之前,你必须转换从集合中读取的每个对象。如果有人不小心插入了错误类型的对象,则在运行时可能会失败。使用泛型,你告诉编译器在集合中允许存放哪些类型的对象。编译器会自动插入强制转换,并在编译时告诉你是否尝试插入错误类型的对象。

阅读更多

Effective Java(三)类和接口

Item 15 最小化类和成员的可访问性

一个设计良好的组件,应该隐藏其内部细节,将 API 与它的实现分离开来,外界只与 API 通信。这也是软件设计的基本原则。

Java中的四种访问级别:

  • private —— 该成员只能在声明它的顶级类内访问。
  • package-private —— 成员可以从被声明的包中的任何类中访问。从技术上讲,如果没有指定访问修饰符(接口除外,它默认是公共的),这是默认访问级别。
  • protected —— 成员可以从被声明的类的子类中访问,以及它声明的包中的任何类。
  • public —— 该成员可以从任何地方被访问。

能用 private 的,绝不用 protected,能用 protected 的,绝不用 public。

阅读更多

Effective Java(二)对象通用的方法

对象通用的方法,指的是 Object 类下的方法,即 toString、equals、hashCode 等等,合理地使用跟重写它们,可以避免很多坑。

Item 10 重写 equals 时请遵守约定

重写 equals 很容易犯错,最好不要去重写,比如下面的情形:

  1. 类的每个实例都是唯一的。显而易见,像 Thread 这样表示活动而不是值的类来说,每个实例都是不一样的。
  2. 类不需要「逻辑相等(logical equality)」
  3. 父类已经重写了 equals 方法,除非有必要,否则子类就不用再去重写了。例如,大多数 List 从 AbstractList 继承了 equals 实现,Map 从 AbstractMap 的 Map 继承了 equals 实现。
阅读更多

Effective Java(一)创建和销毁对象

《Effective Java》这本书算得上有口皆碑了,去年发现出了第三版,趁某东活动入手了一本英文版,粗略了过了一下,这本书给我最大的体会就是它教你如何成为一个真正的 Java 程序员,而不是 CRUD 程序员或 Spring 程序员,读这本书,能让你站在更高的角度和更深层次的视角去剖析 Java 的细节,让人豁然开朗。然而,上半年因为各种原因,瞎忙活了大半年,这本书一直没机会捡起来仔细看。好在最近工作不忙,想起来有这本书,决定一天看两个 Item 。

系列目录:

阅读更多

Java并发编程之异步任务

有时候,我们想在主线程之外执行一些异步任务,不难想到,可以开一个新线程专门去处理某个任务。在 Java 中处理异步任务都有哪些需要注意的呢?

阅读更多

Java简明笔记(十七)注解

注解魔法

注解是一种标记。在 Java 中,随处可见@Override@Deprecated这样的注解。说实话,Java的注解经常不被重视,以至于学习的时候习惯性略过。在学了Spring框架后发现Spring使用了大量的注解来简化开发和配置,回过头来才发现注解的魅力。

阅读更多

Java简明笔记(十六)网络编程

socket_title

前言

网络编程,顾名思义就是编写通过网络通信的计算机程序。提到网络编程,一般指 socket 编程,之前我写过两篇相关的文章,分别是:浅谈 socket 编程Socket编程实践(Java & Python实现),主要侧重于 socket 编程的理解,而这一篇侧重于使用 Java 进行 socket 编程的要点,作为简明笔记,以备后续用到时方便查阅。

阅读更多

Java并发编程之并发工具

Java自带的平台类库(java.util.concurrent)里面包含了很多有用的工具,来帮助我们更好地处理并发问题。这一篇主要介绍一下几类工具:

  1. atomic原子类:AtomicLong
  2. 同步容器类:Vector、Hashtable
  3. 并发容器类:concurrentHashMap、ConcurrentLinkedQueue、BlockingQueue(阻塞队列)
  4. 并发工具类:闭锁(Latch)、栅栏(Barrier)、信号量(Semaphore)
阅读更多

Java虚拟机(五)JVM参数和调优

本地线程分配缓冲(TLAB)

Java虚拟机遇到 new 指令时,需要在堆内存上为新对象分配内存空间。如果堆是规整的,一边是分配过的内存,一边是空闲内存,那只要在中间用一个指针隔开,为新对象分配内存时,指针往后移动相应的空间距离即可。

pointer_move

然而,在多线程环境下,线程A和线程B同时为新对象分配内存,线程A还没来得及改指针位置,线程B也使用了这个位置来分配内存,就会出现问题。有两种方法解决这个问题,第一是采用同步,事实上虚拟机采用的 CAS 失败重试的方式来保证更新内存的原子性。第二种是本地线程分配缓冲(Thread Local Allocation Buffer)。

本地线程分配缓冲会在 Java 堆内存里预先分配一小块内存专门给某个线程用来分配空间,所以不同的线程分配内存是在不同的位置。这样就不会导致冲突。只有当 TLAB 用完并分配新的缓冲区时,才需要同步锁定。

阅读更多

Java简明笔记(十五)Java NIO

什么是 Java NIO

Java NIO, N 可以理解为 New ,也可以理解为 Non-blocking ,是 Java 1.4 之后新的一套区别于标准 Java IO 和 Java Networking 的 API 。NIO 跟传统的 BIO 之间最大的区别在于 NIO 是面向 Channels 和 Buffers 的,一个线程可以通过 Selector 管理多个 Channels ,继而管理多个连接,在线程进行 IO 操作时不会阻塞。

阅读更多

Java虚拟机(四)垃圾回收策略

GC.png

Java虚拟机(二)内存模型和对象创建 这一篇中,我们知道 Java 虚拟机的内存模型包含五个部分:程序计数器、Java虚拟机栈、本地方法栈、堆、方法区。这五个区域也叫运行时数据区域(Runtime Data Area),他们是数据的存储空间。既然是存储空间,那就有可能达到存满的时候,因此,JVM必须配备一个垃圾回收器(Garbage Collection, GC),用于不定期地回收不再需要的内存空间。

事实上,Java的动态内存分配和回收技术已经相当成熟,作为开发者的我们无需手动去分配和释放内存,一切都交给Java虚拟机。那为什么我们还要去了解 GC 和 内存放配呢?原因是:当需要排查各种内存溢出、泄露等问题,或当垃圾收集称为系统达到更高并发量的瓶颈时,我们有必要对这些“自动化”的技术进行监控和调节。

阅读更多

Java虚拟机(三)Class文件结构

class 文件简介

class 文件是javac编译器编译后生成的二进制文件,全部是连续的0/1。可以把 class 文件中的内容分为两种类型:

  1. 无符号数:表示class文件中的值,没有符号,但有长度。u1、u2、u4、u8 (u1表示1字节的无符号数)
  2. :无符号数要么单独存在,要么多个组合成为二维表。

总而言之,class文件中的数据要么是单个值,要么是二维表。


class 文件的组织结构一览

  1. 魔数
  2. 本文件的版本信息
  3. 常量池
  4. 访问标志
  5. 类索引
  6. 父类索引
  7. 接口索引集合
  8. 字段表集合
  9. 方法表集合
阅读更多

Java虚拟机(一)JVM 基础和类的加载

什么是Java虚拟机

Java的理念是“一次编译,到处运行”。我们平时编写的 Java 代码,经过Java编译器编译后会生成一种 .class 文件,称为字节码文件。Java虚拟机(Java Virtual Machine,JVM)就是负责将字节码文件翻译成特定平台下的机器码然后运行的软件,其本身是由C/C++编写

dotclass

JVM 如何让 Java 程序跨平台?

JVM 将字节码翻译成机器码然后运行,也就是说,只要在不同平台上安装对应的 JVM,就可以运行字节码文件,运行我们编写的 Java 程序。

而这个过程,我们编写的 Java 程序没有任何改变,仅仅是通过 JVM 这一 “中间层” ,就能在不同平台上运行,真正实现了 “一次编译,到处运行” 的目的。 需要注意的是,JVM 本身是用 C/C++ 开发的,是编译后的机器码,不能跨平台,不同平台下需要安装不同版本的 JVM。

JVM

阅读更多

Java中的回调机制

什么是回调(CallBack)呢?有一个经典的打电话例子。

有一天小王遇到一个很难的问题,问题是“1 + 1 = ?”,就打电话问小李,小李一下子也不知道,就跟小王说,等我办完手上的事情,就去想想答案,小王也不会傻傻的拿着电话去等小李的答案吧,于是小王就对小李说,我还要去逛街,你知道了答案就打我电话告诉我,于是挂了电话,自己办自己的事情,过了一个小时,小李打了小王的电话,告诉他答案是2

所谓回调:就是A类中调用B类中的某个方法C,然后B类中反过来调用A类中的方法D,D这个方法就叫回调方法。

回调让模块与模块之间解耦,也实现了异步调用。

阅读更多

使用 Mybatis 简化 JDBC 操作

mybatis

Java简明笔记(十三)JDBC 中,使用 JDBC 来操作数据库,并把查询到的数据库信息进行 java 对象的映射(ORM),但是 JDBC 除了需要自己写SQL之外,还必须操作Connection, Statment, ResultSet,显得繁琐和枯燥。于是我们对 JDBC 进行封装,以简化数据库操作。mybatis就是这样的一个框架。

以下简介摘自官方文档

MyBatis是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。

阅读更多

Java中的引用类型

什么是引用类型

引用类型(reference type)是一种基于类的数据类型。Java中,除去基本数据类型外,其它类型都是引用类型。包括Java提供的或者自己定义的class类。

当我们对某个对象声明一个变量的时候,例如

1
Ball b1 = new Ball();

变量 b1 事实上指向了这个对象的引用,而不是对象本身。

1
Ball b2 = b1;

b2 和 b1 都指向了 ball 类的同一个实例。

Java中有四种引用:

  • 强引用(Strong Reference)
  • 软引用(Soft Reference)
  • 弱引用(Weak Reference)
  • 虚引用(Phantom Reference)
阅读更多

Java简明笔记(十四)反射机制

Java 是完全面向对象语言。事实上,我们创建的每一个类,其实也是对象,称为类对象。类对象提供了类的元信息,比如这个类有几种构造方法,有多少个属性,有哪些普通方法等。

meta

Java反射机制主要提供了以下功能:

  • 在运行时判断任意一个对象所属的类;
  • 在运行时构造任意一个类的对象;
  • 在运行时判断任意一个类所具有的成员变量和方法;
  • 在运行时调用任意一个对象的方法;生成动态代理。
阅读更多

Java并发编程之安全性

并发编程显然有很多优势,然而,多线程也带来了一定的风险。例如安全性问题、活跃性问题、性能问题等。

  • 安全性问题: 含义是“永远不发生糟糕的事情”,例如多个线程同时修改一个共享变量,导致结果跟预期不符。
  • 活跃性问题: 关注“某件正确的事情最终会发生”,假若不能,就会产生活跃性问题。例如死锁,A、B进程互相等待对方释放某资源,结果谁也执行不下去。
  • 性能问题: 在解决安全性问题和活跃性问题的时候会带来额外开销,我们必须想办法减少开销。

并发编程的问题,在Java简明笔记(十一) 并发编程中就有提及,这一篇,主要就安全性问题,详细谈谈Java并发编程的问题。

阅读更多

Java简明笔记(十三)JDBC

假设电脑已经安装有 MySQL,并在里面有一些表。现在,我们想通过 Java,来访问数据库里的表。

JDBC (Java DataBase Connection) 指的就是通过Java访问数据库。

这是我的数据库情况。

阅读更多

Java简明笔记(十一)并发编程

关于并发的理论基础,见另一篇 聊聊并发和并发模型


在 Java 中创建线程

在 Java 中,线程是 java.lang.Thread 或其子类中的实例。通过 new 一个线程实例并调用 start 方法来启动线程。

1
2
Thread thread = new Thread();
thread.start();

但是我们总得指定线程做一些事,可以用两种方式来指定。

阅读更多

Java简明笔记(十) 输入与输出

文本输入和输出

文本输入

对于较短的文本,我们可以直接把文本存到一个String里

1
2
3
4
5
// 整个文本
String contents = new String(readAllBytes((Paths.get("alice.txt"))), StandardCharsets.UTF_8);

// 以非字母为分隔符,变成一个个单词
List<String> words = Arrays.asList(contents.split("\\PL+"));

如果想按行读取,可以读文件并存到 List 集合里,集合的每一个元素代表每一行的一个String

1
2
// 按行读取
List<String> lines = Files.readAllLines(path, charset);
阅读更多

Java简明笔记(九)Stream API

Stream

Java 中的 Stream 提供了数据源,让你可以在比集合类更高的概念层上指定操作。使用 Stream,只需要指定做什么,而不是怎么做。你只需要将操作的调度执行留给实现。

简单地说,流就是一组数据,经过某种操作,产生我们所需的新流,或者输出成非流数据。

流的来源,可以是集合,数组,I/O channel, 生成器(generator)等。流的聚合操作类似 SQL 语句,比如filter, map, reduce, find, match, sorted等。

例如,从文件从获取流:

1
2
3
try (Stream<String> lines = Files.lines(Paths.get("/path/to/file.txt"))) {
...
}
阅读更多

Java简明笔记(八)Lambda和函数式编程

函数式编程

我们平时所采用的 命令式编程(OO也是命令式编程的一种)关心解决问题的步骤。你要做什么事情,你得把达到目的的步骤详细的描述出来,然后交给机器去运行。

而函数式编程关心数据的映射,或者说,关心类型(代数结构)之间的关系。这里的映射就是数学上“函数”的概念,即一种东西和另一种东西之间的对应关系。所以,函数式编程的“函数”,是数学上的“函数”(映射),而不是编程语言中的函数或方法

函数式编程的思维就是如何将这个关系组合起来,用数学的构造主义将其构造出你设计的程序。用计算来表示程序,用计算的组合来表达程序的组合。

阅读更多

Java简明笔记(七) 异常和断言

异常处理

在Java异常处理中,一个方法可以通过 抛出(throw) 异常来发出一个严重问题的信号。调用链中的某个方法,负责 捕获(catch) 并处理异常。捕获到的异常不仅可以在当前方法中处理,还可以将异常抛给调用它的上一级方法去处理。

异常处理的根本优点是:将错误检测和错误处理的过程解耦。

Java 的异常都派生自 Throwable 类,Throwable 又分为 Error 和 Exception。Error 不是我们的程序所能够处理的,比如系统内存耗尽。我们能预知并处理的错误属于 Exception。Exception又分为 unchecked exception 和 checked exception。 unchecked exception 属于 RuntimeException 。

当然,所有的异常都发生在运行时(Runtime),但是 RuntimeException 派生的子类异常在编译时不会被检查。

阅读更多

Java中的 String

String 的本质

在 Java8 中,分析 java.lang.String 类的源码,可以发现 String 内部维护的是一个 char 数组。同时可以发现,String类被 final 修饰,即不可变的。

1
2
3
4
5
6
7
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];

//...
}

在 Java9 中,将 char 数组优化成了 byte 数组。

1
private final byte value[];
阅读更多

Java简明笔记(六) 集合

集合和集合框架

集合是什么?

集合就是一个放数据的容器,准确的说是放数据对象引用的容器。

Java集合主要可以划分为三个部分:

  1. Collection(包含 List 和 Set )
  2. Map (键值对)
  3. 工具类(Iterator迭代器、Enumeration枚举类、Arrays和VCollections)
阅读更多

Java简明笔记(五) 泛型编程

什么是泛型类

假设我们现在有一个存储字符串字典键值对的类,就像这样

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Entry {
private int key;
private String value;

// 构造函数:int 类型的 key, String 类型的 value
public Entry(int key, String value) {
this.key = key;
this.value = value;
}

public int getKey() { return key; }
public String getValue() { return value; }
}

在这个类中,我们用 int 类型来存储 key 值, 用 String 类型来存储 value 值。

现在,老板要求,除了 int 类型的 key 和 String 类型的 value之外,还得提供其他类型的 key 和 value 。 比如 double 类型的 key, boolean 类型的value。

我们不可能写很多个相似的类,只是换一下类型。8种基本数据类型或许可以这么干,但是存储的是抽象数据类型呢?我们不可能所有类型都写一个对应的类。

为了解决这个问题,我们可以用 Java 泛型: 只写一个类,实例化的时候再写明是什么类型就好了。这就是泛型类。

注意:泛型仅仅是java的语法糖,它不会影响java虚拟机生成的汇编代码,在编译阶段,虚拟机就会把泛型的类型擦除,还原成没有泛型的代码。

阅读更多

Java简明笔记(四) 继承

什么是继承

继承是在现有的类的基础上创建新类的过程。继承一个类,你也就重用了它的方法,而且还可以添加新的方法和域。

举个例子:员工有薪水,管理者有薪水+奖金, 管理者继承员工,增加 bounus 字段和 setBonus 方法即可。这种情况就是管理者类继承了员工类。

1
2
3
4
5
6
7
8
public class Manager extends Employee {
private double bonus;
...

public void setBonus (double bouns) {
this.bonus = bonus;
}
}

Manager类继承了Employee类,除了获得Employee类的变量和方法外,还额外添加了bonus变量和setBonus方法。

阅读更多

Java简明笔记(三) 接口

什么是接口

假设有一种整数序列服务,这种服务可以计算前n个整数的平均值。就像这样:

1
2
3
4
public static double average(IntSequence seq, int n){
...
return average
}

我们传入一个序列seq,以及我们想计算这个序列的前n个数,它返回平均数。

然而,这样的序列可以有很多种形式,比如用户给出的序列、随机数序列、素数序列、整数数组中的元素序列……

阅读更多

Java简明笔记(二) 面向对象

Java 创建对象的过程

当我们实例化一个对象时,例如:

1
Person p = new Person();

Java首先会在堆中开辟一块内存空间用来存放这个新的 Person 对象,然后在栈中创建一个引用 p , p 指向堆中的 Person 对象。这样,我们通过 p 就能找到 Person 的内存地址。

之后,我们执行:

1
Person p2 = p;

我们创建了一个新的引用 p2, p2 跟 p 一样,都是保存在栈中,也指向了 Person 对象。当我们改变 p2 的状态, p 也会跟着改变,因为他们指向同一个对象。

阅读更多

Java简明笔记(一) 基础知识

java

Java 与 C++ 的区别

  1. C++支持多重继承,Java不支持,但可以实现多接口。(引申:多重继承菱形问题)
  2. 自动内存管理
  3. java不支持goto语句
  4. 引用与指针:在Java中不可能直接操作对象本身,所有的对象都由一个引用指向,必须通过这个引用才能访问对象本身,包括获取成员变量的值,改变对象的成员变量,调用对象的方法等。而在 C++ 中存在引用,对象和指针三个东西,这三个东西都可以访问对象。其实,Java中的引用和C++中的指针在概念上是相似的,他们都是存放的对象在内存中的地址值,只是在Java中,引用丧失了部分灵活性,比如 Java 中的引用不能像 C++ 中的指针那样进行加减运算。
阅读更多
Your browser is out-of-date!

Update your browser to view this website correctly.&npsb;Update my browser now

×