Java基础知识

203 阅读22分钟

面向对象

继承

  • 通过继承多个接口实现
  • 通过内部类进行实现
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
  1. public Constructor<?>[] getConstructors(): 返回类的所有的public构造方法
  2. public Constructor getConstructor(Class<?>... parameterTypes): 返回指定的public构造方法
  3. public Constructor<?>[] getDeclaredConstructors(): 返回类的所有构造方法
  4. public Constructor getDeclaredConstructor(Class<?>... parameterTypes): 返回指定的构造方法
  • 获取类的成员变量的方法,成员变量的封装类为Field
  1. public Field[] getFields(): 获取类的所有public成员变量
  2. public Field getField(String name):获取指定的public成员变量
  3. public Field[] getDeclaredFields(): 获取类的所有成员变量
  4. public Field getDeclaredField(String name):获取任意访问权限的指定名字的成员变量
  • 获取类的方法,方法的封装类为Method
  1. public Method[] getMethods():获取类的所有public方法
  2. public Method getMethod(String name,Class<?>... parameterTypes):获取类的指定的public方法
  3. public Method[] getDeclaredMethods():获取类的所有方法
  4. 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());
	}
}


嵌套类

  • 成员内部类
    1. 可以自由地引用外部类的属性和方法,无论是静态或者非静态
    2. 与实例绑定在一起时,不可定义静态的属性和方法
    3. 不能有静态成员
  • 静态内部类
    1. 可以不依赖外部类实例而被实例化
    2. 不能与外部类有相同的名字
    3. 不能访问外部类的普通成员变量
    4. 只能访问外部类的静态成员和静态方法(包括私有类型)
  • 局部内部类
  • 匿名内部类
    1. 不能有构造函数
    2. 不能定义静态成员、方法和类
    3. 不能是public、protected、private、static
    4. 只能创建匿名内部类的一个实例
    5. 一定是在new 后面,必须继承一个父类或者实现一个接口
    6. 局部内部类的限制都对其有效

泛型

有界泛型(示例)

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新特性

  • 集合框架增强
    1. 添加许多新的类和接口
    2. 新的数组拷贝方法。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

特点

  • 随机访问效率高
  • 读快写慢,由于写的过程需要涉及元素的移动

实现原理

  • 父类和接口

    1. java.util.AbstractList 提供了基本的方法封装,以及通用的迭代器实现
    2. java.util.List 提供对内部元素插入位置的精确控制,用户可以使用整数索引来查询
    3. java.util.RandomAccess 如果类实现了这个接口,则这个类使用索引遍历比迭代器更快
    4. java.lang.Cloneable 用于标记可克隆对象
    5. java.io.Serialzable 序列化标记接口,被此接口标记的类可以实现Java序列化和反序列化
  • 成员变量和常量

    • 成员变量
      1. private transient Object[] elementData,elementData是该List的数据域,其中被transient修饰表示这个变量不会被序列化,通过反射实现间接序列化,因为elementData是一个缓存数组,会预留一些容量,因此会有大量空间没有实际存储元素,通过反射可以只序列化实际有值的元素
      2. private int size 表示当前List的长度,elementData的length是大于或等于Data的
      3. protected transient int modCount =0,记录ArrayList结构性变化的次数
    • 常量
      1. private static final serialVersionUID = 8683452581122892189L,序列化版本UID,为了维持序列化版本的一致性
      2. private static final int MAX_ARRAY_SIZE 数组长度的上限
  • 构造方法

    public ArrayList(int initialCapacity)
    public ArrayList()
    public ArrayList(Collection<? extends E> c)
    

    其中initialCapacity表示初始化的elementData的长度,如果使用无参构造方法,则默认为10

  • 迭代器和索引遍历

    1. 索引遍历使用get(int)方法来获取数据,T1(n)=nθ(1)
    2. 迭代器调用hasNext()判断索引是否和size相等,hasNext()=θ(1),next()进行多个操作,设一个常量a表示这些操作的开销,则next()=aθ(1),则T2(n)=(a+1)nθ(1),只有数据量很大时,索引遍历的效率才更高
  • 方法实现

    • indexof/lastIndexof/contains实现
      1. indexof方法用于查询指定对象的索引,顺序遍历,调用equals方法比对,查询不到,返回-1
      2. lastIndexof,是对数组倒序遍历
      3. contains方法直接调用indexof方法,根据返回值,判断查找的元素是否存在
    • set/add/addAll实现
      1. set实现是替换数组里的对应索引的值
      2. add和addAll方法实现,首先要检查当前elementData的长度,如果添加的长度超过elementData的长度,需要对elementData的容量进行修正。关键方法是grow(int)
    • remove/removeAll/retainAll方法实现
      1. remove有两种重载形式remove(int)和remove(Object)。当形参为int时,移除位于指定index的数据,但是会将index之后的数据往前移;当形参为Object时,会先遍历数组找到与之相等对象,用equals判断,然后执行类似remove(int)的操作
      2. removeAll用于移除指定集合里的所有元素,retainAll是保留指定集合里的元素,都是调用batchRemove(Collection,boolean),区别是传入参数不同,removeAll传入false,retainAll传入true

LinkedList

特点

  • 顺序访问
  • 写快读慢

实现原理

  • 父类和接口
    1. java.util.AbstractSequentialList,继承自AbstractList,提供顺序访问结构
    2. java.util.Deque,双向队列接口,Deque可以同时在头尾处完成读写操作,与此同时,LinkedList还能操作头尾之间的任意结点,在Deque的基础上实现了List
    3. java.lang.Cloneable,java.util.List,java.lang.Serialiable
  • 成员变量和常量
    • 成员变量
      1. transient int size;用于标记序列的大小,因为链表并没有办法获取size,所以采用一个标记量来做记录
      2. transient Node first; 链表的头结点
      3. transient Node last; 链表的尾结点
    • 常量
      1. serialVersionUID,序列化接口常量
  • 构造方法
    public LinkedList()
    public LinkedList(Collection<? extends E> c)
    
    与ArrayList需要一个定长数组不同,链表无需初始化任何对象,所以无参构造方法没有做任何操作
  • 方法实现
    • getFirst/getLast/get方法实现
      1. getFirst和getLast直接返回first.item和last.item即可实现
      2. get(int)方法则是顺序遍历到i结点,时间复杂度为O(n),当然具体实现的时候做了优化,小于size小半就顺序遍历,大于size就倒序遍历
    • set/add/addAll方法实现
      1. LinkedList的add方法比set迅速,add的本质是尾插法,LinkedList维护成员变量last即可,而set则需要遍历结点,再去替换
      2. addAll则是调用add(E)多次
    • removeFirst/removeLast/remove方法实现 不需要移动数组,只需要修改结点后继结点和前驱结点的指向

Vector

  • 与ArrayList的区别
    1. Vector是线程安全的,因为Vector的所有public方法都使用synchronize关键字
    2. Vector多了成员变量capacityIncrement,用于标明扩容的增量,而ArrayList固定扩容50%

Stack

  • 与Vector的区别
    1. 多了表达栈含义的方法,如: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

    底层实现是数组和链表

    • 成员变量

      1. 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的查询效率

      2. transient int size;//HashMap中键值对数量

      3. final float loadFactor;//加载因子,用于决定table的扩容量

    • 常量

      1. static final int DEFAULT_INITIAL_CAPACITY=16;//默认的初始化容量
      2. static final int MAXIMUM_CAPACITY= 1<<30;//最大容量,会跟构造函数指定 HashMap容量时,作比较
      3. 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;
      }
      
      1. indexFor(int,int)方法,作用是根据hashCode和table长度来计算下标

        static int indexFor(int h,int length){
        	return h & (length-1);
        }
        

        核心为h&(length-1),h为hashCode,length为table长度

      2. 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时必须满足两个条件:

        1. 虚拟机已经启动
        2. 设定的容量超出虚拟机设定的某个替代哈希散列算法阈值
      3. addEntry(int,K,V,int)和createEntry(int,K,V,int),添加键值对

      4. resize(int),用于给HashMap扩充容量

      5. 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)方法详解

      1. 获取key值的hashCode
      static final int hash(Object key){
      	int h;
      	return (key == null)? 0 : (h=key.hashCode())^(h>>>16);
      }
      
      1. 调用putVal方法进行存值
      final V putVal(int hash,K key,V value,boolean onlyIfAbsent,boolean evict){...}
      
      1. 计算下标,与历史版本一致
      2. 当table为空,或者数据数量超过扩容阈值的时候,重新计算table长度
      3. 保存数据
        • 当下标位置没有结点的时候,直接增加一个链表结点
        • 当下标位置结点为树节点的时候,增加一个树节点
        • 当前面情况不满足,说明当前下标位置有结点,且为链表结点,遍历链表,根据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方法

    1. 如果TreeMap是空的,那么使用指定数据作为根结点
    2. 反之,如果comparator不为空,那么使用comparator来决定插入位置;comparator为空,那么认为key值实现了Comparable,直接调用compareto方法来决定插入位置;如果key没有实现Comparable,抛出异常
    3. 插入完成后,修复红黑树

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);
        }
    }
    

    由上面源码可知:

    1. HashTable被synchronized修饰,HashTable是线程安全的
    2. 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;
    }
}
  1. Entry继承自WeakReference
  2. 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();
		}
	}
}

NIO