面向对象
继承
- 通过继承多个接口实现
- 通过内部类进行实现
package test;
//继承
public class Extends {
//通过接口实现
interface Fly{
public void fly();
}
interface Run{
public void run();
}
class Animals{
public void getCatagory() {
System.out.println("I'am animal");
}
}
class Duck extends Animals implements Fly,Run{
public void fly() {
System.out.println("I can fly");
}
public void run() {
System.out.println("I can run");
}
}
//通过内部类实现
class Memory{
public void m() {
System.out.println("Memory");
}
}
class CPU{
public void c() {
System.out.println("CPU");
}
}
class Computer{
class Memory1 extends Memory{};
class CPU1 extends CPU{};
public void m() {
new Memory1().m();
}
public void c() {
new CPU1().c();
}
}
public static void main(String[] args) {
Extends e = new Extends();
Duck d = e.new Duck();
d.getCatagory();
d.fly();
d.run();
Computer c = e.new Computer();
c.m();
c.c();
}
}
多态
- 重载(编译时期的多态) 同一个类有多个同名的方法,方法有不同的参数
- 覆盖(运行期间的多态)
因为子类可以覆盖父类的方法,所以只有在运行期间程序才能将方法动态绑定到运行中的对象
注意:只有类中的方法才有多态的概念,类中的成员变量没有多态的概念
反射
反射的功能
- 获取类的访问修饰符、方法、属性以及父类信息
- 在运行时根据类的名字创建对象。在运行时调用任意一个对象的方法
- 在运行时判断一个对象属于哪个类
- 生成动态代理
获取Class对象的方法
- 方法一不执行静态块和动态构造块
- 方法二只执行静态块、而不执行动态构造块
- 方法三因为需要创建对象,所以会执行静态块和动态构造块
package test;
//反射
public class Reflect {
static {
System.out.println("static block");
}
{
System.out.println("dynamic block");
}
public static void main(String[] args) {
//通过className.class来获取
Class<?> c = Reflect.class;
System.out.println("className:"+c.getName());
//通过Class.forName来获取
Class<?> d = null;
try {
d = Class.forName("test.Reflect");//需要加上包路径
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("className1:"+d.getName());
//通过Object.getClass()来获取
Class<?> e = new Reflect().getClass();
System.out.println("className2:"+e.getName());
Class<?> f = new Reflect().getClass();
System.out.println("className2:"+f.getName());
}
}
Class类的常用方法
- 获取类的构造方法,构造方法的封装类为Constructor
- public Constructor<?>[] getConstructors(): 返回类的所有的public构造方法
- public Constructor getConstructor(Class<?>... parameterTypes): 返回指定的public构造方法
- public Constructor<?>[] getDeclaredConstructors(): 返回类的所有构造方法
- public Constructor getDeclaredConstructor(Class<?>... parameterTypes): 返回指定的构造方法
- 获取类的成员变量的方法,成员变量的封装类为Field
- public Field[] getFields(): 获取类的所有public成员变量
- public Field getField(String name):获取指定的public成员变量
- public Field[] getDeclaredFields(): 获取类的所有成员变量
- public Field getDeclaredField(String name):获取任意访问权限的指定名字的成员变量
- 获取类的方法,方法的封装类为Method
- public Method[] getMethods():获取类的所有public方法
- public Method getMethod(String name,Class<?>... parameterTypes):获取类的指定的public方法
- public Method[] getDeclaredMethods():获取类的所有方法
- public Method getDeclaredMethod(String name,Class<?>... parameterTypes):获取类的指定的方法
package test;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
//Class类的常用方法
public class Test {
protected Test() {
System.out.println("Protected constructor");
}
public Test(String name) {
System.out.println("Public constructor");
}
public void f() {
System.out.println("f()");
}
public void g(int i) {
System.out.println("g():"+i);
}
class Inner{
}
class ReadOnlyClass{
private Integer age = 20;
public Integer getAge() {
return age;
}
}
public static void main(String[] args) throws Exception {
Test test = new Test();
Class<?> clazz = Class.forName("test.Test");//main函数调用它必须要加个处理异常
Constructor<?>[] constructors = clazz.getDeclaredConstructors();
System.out.println("Test类的构造函数:");
for(Constructor<?> c:constructors) {
System.out.println(c);
}
Method[] methods = clazz.getMethods();
System.out.println("Test类的全部方法");
for(Method m:methods) {
System.out.println(m);
}
Class<?>[] inners = clazz.getDeclaredClasses();
System.out.println("Test类的内部类为:");
for(Class<?> c: inners) {
System.out.println(c);
}
//通过反射修改私有变量
ReadOnlyClass pt = test.new ReadOnlyClass();
Class<?> cla = ReadOnlyClass.class;
Field field = cla.getDeclaredField("age");
field.setAccessible(true);//为true时表示反射对象在使用时应该取消Java语言访问检查,也可以提升反射速度
field.set(pt, 30);
System.out.println(pt.getAge());
}
}
嵌套类
- 成员内部类
- 可以自由地引用外部类的属性和方法,无论是静态或者非静态
- 与实例绑定在一起时,不可定义静态的属性和方法
- 不能有静态成员
- 静态内部类
- 可以不依赖外部类实例而被实例化
- 不能与外部类有相同的名字
- 不能访问外部类的普通成员变量
- 只能访问外部类的静态成员和静态方法(包括私有类型)
- 局部内部类
- 匿名内部类
- 不能有构造函数
- 不能定义静态成员、方法和类
- 不能是public、protected、private、static
- 只能创建匿名内部类的一个实例
- 一定是在new 后面,必须继承一个父类或者实现一个接口
- 局部内部类的限制都对其有效
泛型
有界泛型(示例)
Parent类
package test;
public class Parent {
}
Sub1类和Sub2类,继承Parent类
package test;
public class Sub1 extends Parent{
}
package test;
public class Sub2 extends Parent{
}
上界泛型类
package test;
//有上界的泛型类
public class ExtendSample <T extends Parent>{
T obj;
//有上界的泛型方法
<K extends Sub1> T extendMethod(K param) {
return this.obj;
}
}
下界泛型类
package test;
public class SuperSample<T> {
T obj;
}
Test1类
package test;
public class Test1 {
public static void main(String[] args) {
//上界测试
ExtendSample<Parent> sample1 = new ExtendSample<Parent>();
ExtendSample<Sub1> sample2 = new ExtendSample<Sub1>();
ExtendSample<? extends Parent> sample3 = new ExtendSample<Sub1>();
ExtendSample<? extends Sub1> sample4;
//(编译错误)Type mismatch: cannot convert from ExtendSample<Sub2> to ExtendSample<? extends Sub1>
//sample4 = new ExtendSample<Sub2>();
//(编译错误)Bound mismatch: The type ? extends Number is not a valid substitute for the bounded parameter <T extends Parent> of the type ExtendSample<T>
//ExtendSample<? extends Number> sample5;
sample1.obj = new Sub1();
//(编译错误)Type mismatch: cannot convert from Parent to capture#1-of ? extends Parent
//sample3.obj = new Parent();
//下界测试
SuperSample<? super Parent> supSample1 = new SuperSample<Parent>();
//(编译错误)Type mismatch: cannot convert from SuperSample<Sub1> to SuperSample<? super Parent>
//SuperSample<? super Parent> supSample2 = new SuperSample<Sub1>();
SuperSample<? super Sub1> supSample3 = new SuperSample<Parent>();
supSample1.obj = new Sub1();
supSample1.obj = new Sub2();
supSample1.obj = new Parent();
supSample3.obj = new Sub1();
//(编译错误)Type mismatch: cannot convert from Sub2 to capture#5-of ? super Sub1
//supSample3.obj = new Sub2();
//(编译错误)Type mismatch: cannot convert from Parent to capture#5-of ? super Sub1
//supSample3.obj = new Parent();
}
}
泛型擦除
(注意:Java的泛型不存在运行时)
为什么需要泛型擦除呢?首先需要了解泛型如何实现的
-
Code specialization
使用这种方法,每当实例化一个泛型类的时候都会产生一份新的字节代码,如使用ArrayList,ArrayList初始化两个实例时候就会针对String和Integer生成两份单独的代码,导致代码膨胀,浪费空间
-
Code sharing
使用这种方式,会对每个泛型类只生成唯一的一份目标代码,所有泛型的实例会被映射到这份目标代码上,在需要的时候执行特定的类型检查
泛型擦除的定义
指在编译器处理带泛型定义的类、接口或方法时,会在字节码指令集里抹去全部泛型类型信息,泛型被擦出后在字节码中只保留泛型的原始类型 (一般而言是泛型的定义上界,如<T extends String>对应的原始类型就是String)
泛型擦除带来的常见问题
(之后补充具体样例)
- 泛型类型变量不能是基本数据类型
- 类型的丢失
- catch中不能使用泛型异常类
- 泛型类的静态方法与属性不能使用泛型
Java新特性
Java5新特性
- 泛型
- 增强循环
- 自动封箱拆箱
- 枚举
- 可变参数
- 静态导入
- 注解,关键字@interface
- 新的线程模型和并发库
Java6新特性
- 集合框架增强
- 添加许多新的类和接口
- 新的数组拷贝方法。Arrays.copyOf和Arrays.copyOfRange
- Scripting
- 支持JDBC4.0规范
Java7新特性
- switch中添加对String类型的支持
- 数字字面量的改进/数值可加下划
- 异常处理(捕获多个异常)
- 增强泛型推断
- 增加二进制表示
- try-with-resources语句
Java8新特性
- Lambda表达式
- 方法的默认实现和静态方法
- 方法引用
- 注解
- 类型推测
- 参数名字
- 新增Optional类
- 新增Stream类
- 日期新特性
- 调用JavaScript
- Base64
- 并行数组
Java9新特性
- Jshell:交互式Java REPL
- 不可变集合工厂方法
- 私有接口方法
- 平台级模块系统
- 进程API的改进
- try-with-resources
- Stream API的改进
实现Collection接口的容器
ArrayList
特点
- 随机访问效率高
- 读快写慢,由于写的过程需要涉及元素的移动
实现原理
-
父类和接口
- java.util.AbstractList 提供了基本的方法封装,以及通用的迭代器实现
- java.util.List 提供对内部元素插入位置的精确控制,用户可以使用整数索引来查询
- java.util.RandomAccess 如果类实现了这个接口,则这个类使用索引遍历比迭代器更快
- java.lang.Cloneable 用于标记可克隆对象
- java.io.Serialzable 序列化标记接口,被此接口标记的类可以实现Java序列化和反序列化
-
成员变量和常量
- 成员变量
- private transient Object[] elementData,elementData是该List的数据域,其中被transient修饰表示这个变量不会被序列化,通过反射实现间接序列化,因为elementData是一个缓存数组,会预留一些容量,因此会有大量空间没有实际存储元素,通过反射可以只序列化实际有值的元素
- private int size 表示当前List的长度,elementData的length是大于或等于Data的
- protected transient int modCount =0,记录ArrayList结构性变化的次数
- 常量
- private static final serialVersionUID = 8683452581122892189L,序列化版本UID,为了维持序列化版本的一致性
- private static final int MAX_ARRAY_SIZE 数组长度的上限
- 成员变量
-
构造方法
public ArrayList(int initialCapacity) public ArrayList() public ArrayList(Collection<? extends E> c)其中initialCapacity表示初始化的elementData的长度,如果使用无参构造方法,则默认为10
-
迭代器和索引遍历
- 索引遍历使用get(int)方法来获取数据,T1(n)=nθ(1)
- 迭代器调用hasNext()判断索引是否和size相等,hasNext()=θ(1),next()进行多个操作,设一个常量a表示这些操作的开销,则next()=aθ(1),则T2(n)=(a+1)nθ(1),只有数据量很大时,索引遍历的效率才更高
-
方法实现
- indexof/lastIndexof/contains实现
- indexof方法用于查询指定对象的索引,顺序遍历,调用equals方法比对,查询不到,返回-1
- lastIndexof,是对数组倒序遍历
- contains方法直接调用indexof方法,根据返回值,判断查找的元素是否存在
- set/add/addAll实现
- set实现是替换数组里的对应索引的值
- add和addAll方法实现,首先要检查当前elementData的长度,如果添加的长度超过elementData的长度,需要对elementData的容量进行修正。关键方法是grow(int)
- remove/removeAll/retainAll方法实现
- remove有两种重载形式remove(int)和remove(Object)。当形参为int时,移除位于指定index的数据,但是会将index之后的数据往前移;当形参为Object时,会先遍历数组找到与之相等对象,用equals判断,然后执行类似remove(int)的操作
- removeAll用于移除指定集合里的所有元素,retainAll是保留指定集合里的元素,都是调用batchRemove(Collection,boolean),区别是传入参数不同,removeAll传入false,retainAll传入true
- indexof/lastIndexof/contains实现
LinkedList
特点
- 顺序访问
- 写快读慢
实现原理
- 父类和接口
- java.util.AbstractSequentialList,继承自AbstractList,提供顺序访问结构
- java.util.Deque,双向队列接口,Deque可以同时在头尾处完成读写操作,与此同时,LinkedList还能操作头尾之间的任意结点,在Deque的基础上实现了List
- java.lang.Cloneable,java.util.List,java.lang.Serialiable
- 成员变量和常量
- 成员变量
- transient int size;用于标记序列的大小,因为链表并没有办法获取size,所以采用一个标记量来做记录
- transient Node first; 链表的头结点
- transient Node last; 链表的尾结点
- 常量
- serialVersionUID,序列化接口常量
- 成员变量
- 构造方法
与ArrayList需要一个定长数组不同,链表无需初始化任何对象,所以无参构造方法没有做任何操作public LinkedList() public LinkedList(Collection<? extends E> c) - 方法实现
- getFirst/getLast/get方法实现
- getFirst和getLast直接返回first.item和last.item即可实现
- get(int)方法则是顺序遍历到i结点,时间复杂度为O(n),当然具体实现的时候做了优化,小于size小半就顺序遍历,大于size就倒序遍历
- set/add/addAll方法实现
- LinkedList的add方法比set迅速,add的本质是尾插法,LinkedList维护成员变量last即可,而set则需要遍历结点,再去替换
- addAll则是调用add(E)多次
- removeFirst/removeLast/remove方法实现 不需要移动数组,只需要修改结点后继结点和前驱结点的指向
- getFirst/getLast/get方法实现
Vector
- 与ArrayList的区别
- Vector是线程安全的,因为Vector的所有public方法都使用synchronize关键字
- Vector多了成员变量capacityIncrement,用于标明扩容的增量,而ArrayList固定扩容50%
Stack
- 与Vector的区别
- 多了表达栈含义的方法,如:push,pop,peek,empty,search
总结
1. ArrayList读取快是因为get(int)直接从数组获取数据,写慢是指容量发生变化的时候写慢,容量不发生变化的时候一样很快
2. LinkedList查询数据会消耗时间,在头尾增删数据很快,做随机插入,还是需要遍历
3. Vector和ArrayList相比,Vector是线程安全的,而且容量增长策略不同
4. Stack是Vector的子类,提供了一些栈特性的方法
Queue (后面补上)
实现Map接口的容器
HashMap
- hashCode在HashMap中的作用
package test;
import java.util.HashMap;
public class HashMapExample {
static class HS{
public int hashCode() {return 1;}
}
public static void main(String[] args) {
HashMap<HS,String> map = new HashMap<HS,String>();
map.put(new HS(), "1");
map.put(new HS(),"2");
System.out.println(map);
map.put(new HS() {
@Override
public boolean equals(Object obj) {
return true;
}
},"3");
System.out.println(map);
map.put(new HS() {
public int hashCode() {
return 2;
}
public boolean equals(Object obj) {
return true;
}
},"3");
System.out.println(map);
}
}
-
Java8之前的HashMap
底层实现是数组和链表
-
成员变量
-
transient Entry<K,V>[] table;//存储数据的核心成员变量
table是HashMap的核心成员变量。该数组记录HashMap的所有数据,每一个下标对应一个链表,Entry<K,V>则是该链表的结点元素。
Entry数组的两个特性: 1. 会在特定的时刻,根据需要来扩容 2. 其长度始终保持为2的幂次方
如:字符串“1”的hashCode经计算得到51,被作为键值存入HashMap后,table[51]对应的Entry.key就是“1”
结论:尽量使键值的hashCode分散,这样可以提高HashMap的查询效率
-
transient int size;//HashMap中键值对数量
-
final float loadFactor;//加载因子,用于决定table的扩容量
-
-
常量
- static final int DEFAULT_INITIAL_CAPACITY=16;//默认的初始化容量
- static final int MAXIMUM_CAPACITY= 1<<30;//最大容量,会跟构造函数指定 HashMap容量时,作比较
- static final float DEFAULT_LOAD_FACTOR=0.75f//默认加载因子
-
put(K,V) (流程图后面补充)
public V put(K key,V value){ if(key == null){ return putForNullKey(value); } int hash = hash(key); int i = indexFor(hash,table.length); for(Entry<K,V> e=table[i];e!=null;e=e.next){ Object k; if(e.hash == hash && ((k=e.key) == key||key.equals(k))){ V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } } modCount++; addEntry(hash,key,value,i); return null; }-
indexFor(int,int)方法,作用是根据hashCode和table长度来计算下标
static int indexFor(int h,int length){ return h & (length-1); }核心为h&(length-1),h为hashCode,length为table长度
-
hash(Object k)方法,用于计算键值k的hashCode
final int hash(Object k){ int h = 0; if(useAltHashing){ if(k instanceof String){ return sun.misc.Hashing.stringHash32((String) k); } } h=hashSeed; } h ^= k.hashCode(); h ^=(h>>>20)^(h>>>12); return h^(h>>>7)^(h>>>4);松散哈希:尽可能平衡分布hashCode
useAltHashing是个标识量,值为true时必须满足两个条件:
- 虚拟机已经启动
- 设定的容量超出虚拟机设定的某个替代哈希散列算法阈值
-
addEntry(int,K,V,int)和createEntry(int,K,V,int),添加键值对
-
resize(int),用于给HashMap扩充容量
-
transfer(Entry[] boolean),重新计算转移到新table数组后的Entry下标
-
put功能总结:
1. 计算键值的hash值 2. 根据hash值和table长度来确定下标 3. 存入数组 4. 根据key值和hash值来比对,确定是创建链表结点还是替代之前的链表值 5. 根据增加后的size来扩容,确定下一个扩容阈值,确定是否需要使用替代哈希算法 -
-
Java8提供的HashMap
实现方式是数组+树+链表
transient Node<K,V>[] table;-
put(K,V)方法详解
- 获取key值的hashCode
static final int hash(Object key){ int h; return (key == null)? 0 : (h=key.hashCode())^(h>>>16); }- 调用putVal方法进行存值
final V putVal(int hash,K key,V value,boolean onlyIfAbsent,boolean evict){...}- 计算下标,与历史版本一致
- 当table为空,或者数据数量超过扩容阈值的时候,重新计算table长度
- 保存数据
- 当下标位置没有结点的时候,直接增加一个链表结点
- 当下标位置结点为树节点的时候,增加一个树节点
- 当前面情况不满足,说明当前下标位置有结点,且为链表结点,遍历链表,根据hash和key判断是否重复
- 如果链表深度达到或超过树阈值(TREEIFY_THRESHOLD-1)时,会把整个链表重构成树。TREEIFY_THRESHOLD一般是8
-
TreeMap
TreeMap完全是红黑树实现的
-
成员变量
//比较器,决定了结点在树中分布 private final Comparator<? super K> comparator //树的根节点 private transient Entry<K,V> root //树中包含的实体数目 private transient int size = 0 -
构造方法
public TreeMap() 无参构造,初始化comparator = null public TreeMap(Comparator<? super K> comparator) 比较器构造,使用外部传入的比较器 public TreeMap(Map<? extends K,? extends V> m) 使用传入的Map初始化TreeMap的内容 public TreeMap(SortedMap<K,? extends V> m) 使用SortedMap初始化TreeMap内容,同时使用SortedMap的比较器来初始化TreeMap比较器 -
put方法
- 如果TreeMap是空的,那么使用指定数据作为根结点
- 反之,如果comparator不为空,那么使用comparator来决定插入位置;comparator为空,那么认为key值实现了Comparable,直接调用compareto方法来决定插入位置;如果key没有实现Comparable,抛出异常
- 插入完成后,修复红黑树
LinkedHashMap
-
Java8之前的LinkedHashMap
- 成员变量
//双向链表的表头 private transient Entry<K,V> header; //访问顺序,true为顺序访问,false为逆序访问 private final boolean accessOrder;- createEntry(hash,key,value,index)方法 (HashMap和LinkedHashMap的主要区别)
HashMap的createEntry方法
void createEntry(int hash,K key,V value,int bucketIndex){ Entry<K,V> e = table[bucketIndex]; table[bucktIndex] = new Entry<>(hash,key,value,e); size++; }LinkedHashMap的createEntry方法
void createEntry(int hash,K key,V value,int bucketIndex){ HashMap.Entry<K,V> old = table[bucketIndex]; //这里的Entry是HashMap.Entry的子类,但是多出了双向链表相关方法 Entry<K,V> e = new Entry<>(hash,key,value,old); } -
Java8的LinkedHashMap
- 成员变量
//双向链表表头,最旧的结点 transient LinkedHashMap.Entry<K,V> head; //双向链表表尾,最新的结点 transient LinkedHashMap.Entry<K,V> tail; //迭代顺序,true为顺序,false为逆序 final boolean accessOrder- LinkNodeLast方法 (没懂)
- transferLinks方法 (没懂)
- 如何使用LinkedHashMap (没懂)
HashTable
-
与HashMap的区别
以put方法为例,HashTable的源码
public synchronized V put(K key,V value){ if(value == null){ throw new NullPointerException(); } }HashMap的源码
public V put(K key,V value){ if(key == null){ return putForNullKey(value); } }由上面源码可知:
- HashTable被synchronized修饰,HashTable是线程安全的
- HashTable不能存放null值作为key值,HashMap会把null key存在下标0的位置
WeakHashMap
Java的引用类型
-
ReferenceQueue,引用队列
-
HardReference,强引用,类似String str = new String()建立起来的引用都是强引用
在str指向另一个对象或者null之前,该对象都不会被GC
-
SoftReference,软引用,可以通过java.lang.ref.SoftReference来建立
当GC要求回收时,不会阻止对象被回收,但是回收过程会被延迟,必须等到JVMheap内存不够用,接近OutOfMemory错误时,才会被回收
-
WeakReference,弱引用,可以通过java.lang.ref.WeakReference来建立
当GC要求回收,不会阻止对象被回收,即使有弱引用存在,也会立刻被回收
-
PhantomReference,虚引用,可以通过java.lang.ref.PhantomPeference来建立
大多数时间无法通过它拿到其引用的对象,但是,当这个对象死亡时,该引用还是会进入ReferenceQueue队列
package test;
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.lang.ref.SoftReference;
import java.lang.ref.WeakReference;
class Ref{
Object v;
Ref(Object v){
this.v = v;
}
public String toString() {
return this.v.toString();
}
}
public class Test2 {
public static void main(String[] args) {
Ref wr = new Ref("Hard");
ReferenceQueue<Ref> queue = new ReferenceQueue<Ref>();
//创建一个弱引用
//WeakReference<Ref> weak = new WeakReference<Ref>(new Ref("Weak"),queue);
WeakReference<Ref> weak = new WeakReference<Ref>(wr,queue);
//创建一个虚引用
//PhantomReference<Ref> phantom = new PhantomReference(new Ref("Phantom"),queue);
PhantomReference<Ref> phantom = new PhantomReference(wr,queue);
//创建一个软引用
SoftReference<Ref> soft = new SoftReference(new Ref("Soft"),queue);
System.gc();
System.out.println("引用内容:");
System.out.println(weak.get());
System.out.println(phantom.get());
System.out.println(soft.get());
System.out.println("被回收的引用");
for(Reference r = null;(r = queue.poll())!=null;) {
System.out.println(r);
}
}
}
- WeakHashMap的实现方式
private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V>{
Entry(Object key,V value,ReferenceQueue<Object> queue,int hash,Entry<K,V> next){
super(key,queue);
this.value = value;
this.hash = hash;
this.next = next;
}
}
- Entry继承自WeakReference
- Entry本身没有保存key值,而是把key直接交给了父类WeakReference来构造
Set (后面补充)
JUC框架
AQS (队列同步器)
特点:AQS是一个同步器+阻塞锁的基本架构,用于控制加锁和释放锁,并在内部维护一个FIFO的线程等待队列
实现:使用双向链表实现的FIFO线程等待队列,只有当Head结点持有的线程释放了资源,下一个线程才能获得资源,state是AQS的同步状态量 (PS:这里需要给个图,以后来完善)
资源共享方式:
- Exclusive(独占,在特定的时间内只有一个线程能执行,如ReentrantLock)
- Share(共享,多个线程可同时执行,如CountDownLatch)
state:AQS的同步状态关键字(定义:private volatile int state)
volatile 关键字
(注意:volatie的使用是为了线程安全,但volatile不保证线程安全)
(PS:线程安全的三要素:可见性,有序性,原子性)
三大作用:
- volatile用于解决多核CPU高速缓存导致的变量不同步 (可见性的体现) (实现原理为内存屏障)
- volatile可以解决指令重排序 (有序性的体现)
- volatile不保证操作的原子性 (AQS采用CAS保证原子性,即Compare And Swap)
CAS
作用: 对指定内存地址的数据,校验它的值是否为期望值,如果是,那么修改为新值,返回值表示是否修改成功
执行方式: 自旋锁
ReentrantLock 重入锁
定义: 指在同一线程中,外部方法获得锁之后,内部递归方法依然可以获取该锁
原理: ReentrantLock是一种显示锁,与synchronized隐式锁对应,ReentrantLock能够显示对Lock对象进行操作
种类:
- 公平锁
- 非公平锁
两种锁区别
- FairSync保证了FIFO,先入队的等待线程会最先获得锁,而NonfairSync任由各个等待线程竞争
- 由于FairSync要保证有序性,所以NonfairSync的性能更高,ReentrantLock默认使用NonfairSync
ReentrantLock如何处理重入性(tryAcquire方法里面进行处理,原理为计数器,记录访问次数)
- 判断state标量是否为0,如果为0,那么说明没有线程持有锁,当前线程可以持有锁,返回true
- 如果state不为0,那么判断当前线程是否为锁持有者
- 如果不是,那么当前线程不能持有锁,返回false
- 如果是,那么当前线程已经持有锁,此时认为同线程请求次数增加,state需要增加acquires次,acquires表示新增的请求锁次数
(备注: tryAcquire是请求锁的方法,与之相对应的是tryRelease方法来释放锁)
加锁两种基本形式
- 互斥锁: 通过阻塞线程来进行加锁,中断阻塞来解锁
- 自旋锁: 线程保持运行态,用一个循环体不停地判断某个标识量的状态来确定加锁还是解锁
ReentrantLock和synchronized
-
synchronized使用的是Object对象内置的监视器,且只能有一个监视器,调用notifyAll,那么会唤醒所有线程;
ReentrantLock使用的是条件监视器Condition,通过ReentratnLock.newCondition()方法来获取,同一个ReentrantLock可以创建多个Condition实例,每个Condition维护自己的等待线程队列,调用signalAll只会唤醒自己队列内的线程
-
ReentrantLock更灵活,可以指定公平锁或非公平锁,synchronize限制为非公平锁;ReentrantLock的监视器支持多个队列,在释放锁进入wait状态时可以不响应中断,释放锁进入waittimeout状态时可以不响应中断
监视器Condition的使用
- 监视器的wait和notify操作会改变线程在等待队列里的状态,这个状态时所有线程可见的,必须保证线程安全,所以一定要有锁支撑
- 监视器的notify方法并不会直接唤醒线程,它只会改变线程在等待队列里的状态,真正的唤醒操作是AQS(抽象队列同步器)
Java IO
-
多线程环境下的概念
同步和异步关注的是任务是否可以同时被调用
- 同步:一个执行块同一时间只有一个线程可以访问
- 异步:多个执行块可以同时被多个线程访问
阻塞和非阻塞关注的是线程的状态
- 阻塞:表示线程挂起
- 非租塞:表示线程没有挂起
-
IO语境下的概念
同步和异步关注的是消息发起和接收的机制
- 同步:指发起一个IO操作时,在没有得到结果之前,该操作不返回结果,只有调用结束后,才能获取返回值并继续执行后续的操作
- 异步:指发起一个IO操作后,不会得到返回,结果由发起者自己轮询,或者IO操作的执行者发起回调
阻塞和非阻塞关注的是发起者等待结果时的状态
- 阻塞:发起者在发起IO操作后,不能再处理其他业务
- 非阻塞:发起者不会等待IO操作完成,
BIO
服务器端的实现是一个连接只有一个线程处理,线程在发起请求后,会等待链接返回
//常见同步阻塞IO访问代码
ServerSocket server = null;
try{
server = new ServerSocket(8088);
while(true){
new Thread(new SocketHandler(server.accept())).start();
}
}
catch(IOExcetion e){
e.printStackTrace();
}
finally{
if(server != null){
try{
server.close();
}
catch(IOExcetion e){
e.printStackTrace();
}
}
}