Java
数组和容器类
容器类源码
1. ArrayList
/*
一些参数:--------------------------------------------------------------------------
*/
// 未定义大小时的默认大小
private static final int DEFAULT_CAPACITY = 10;
// 指定大小为 0 时使用该数组
private static final Object[] EMPTY_ELEMENTDATA = {};
// 未指定大小时使用该数组
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
// 存储数据的底层Object数组
transient Object[] elementData;
// 数组的元素个数(并不是数组的大小),当添加或删除元素时会改变此值
private int size;
/*
构造方法:--------------------------------------------------------------------------
*/
// 使用默认空数组,当添加 1 个元素后,大小会增长为10
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
// 使用指定的空数组,当添加 1 个元素后,大小会增长为1
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
// 从另一个List中复制
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
this.elementData = EMPTY_ELEMENTDATA;
}
}
/*
关于扩充--------------------------------------------------------------------------
*/
// 主要用来检查是否是未定义大小的初始化数组,如果是则返回10,否则返回传进来的值
private static int calculateCapacity(Object[] elementData, int minCapacity) {
if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
return Math.max(DEFAULT_CAPACITY, minCapacity);
}
return minCapacity;
}
private void ensureCapacityInternal(int minCapacity) {
ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
// 判断是否需要扩充
private void ensureExplicitCapacity(int minCapacity) {
modCount++;
//如果当前需要的最小空间比当前数组的长度要大,则需要扩充
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
// 扩充为原数组的1.5倍,如果还不够,则扩充为当前所需的最小容量(即minCapacity)
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
/*
添加元素--------------------------------------------------------------------------
*/
//先检查值,再从数组的size处添加元素
public boolean add(E e) {
// 确保有size + 1的空间才能够放下新添加的元素
ensureCapacityInternal(size + 1);
elementData[size++] = e;
return true;
}
public void add(int index, E element) {
rangeCheckForAdd(index);
ensureCapacityInternal(size + 1);
System.arraycopy(elementData, index, elementData, index + 1, size - index);
elementData[index] = element;
size++;
}
/*
删除元素--------------------------------------------------------------------------
*/
// 删除指定位置,数组大小不会改变
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index, numMoved);
elementData[--size] = null;
return oldValue;
}
// 删除指定元素,数组大小不会发生改变
public boolean remove(Object o) {
if (o == null) {
for (int index = 0; index < size; index++)
if (elementData[index] == null) {
fastRemove(index);
return true;
}
} else {
for (int index = 0; index < size; index++)
if (o.equals(elementData[index])) {
fastRemove(index);
return true;
}
}
return false;
}
2. LinkedList
/*
一些参数--------------------------------------------------------------------------
*/
transient int size = 0;
transient Node<E> first;
transient Node<E> last;
/*
构造方法--------------------------------------------------------------------------
*/
public LinkedList() {
}
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
/*
关于增加和删除元素--------------------------------------------------------------------------
*/
// 在头部添加元素
private void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
modCount++;
}
// 在尾部添加元素
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null)
first = newNode;
else
l.next = newNode;
size++;
modCount++;
}
// 在指定位置前添加元素
void linkBefore(E e, Node<E> succ) {
final Node<E> pred = succ.prev;
final Node<E> newNode = new Node<>(pred, e, succ);
succ.prev = newNode;
if (pred == null)
first = newNode;
else
pred.next = newNode;
size++;
modCount++;
}
//删除头结点
private E unlinkFirst(Node<E> f) {
final E element = f.item;
final Node<E> next = f.next;
f.item = null;
f.next = null;
first = next;
if (next == null)
last = null;
else
next.prev = null;
size--;
modCount++;
return element;
}
//删除尾结点
private E unlinkLast(Node<E> l) {
final E element = l.item;
final Node<E> prev = l.prev;
l.item = null;
l.prev = null;
last = prev;
if (prev == null)
first = null;
else
prev.next = null;
size--;
modCount++;
return element;
}
//删除指定节点
E unlink(Node<E> x) {
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
modCount++;
return element;
}
/*
关于增加和删除元素--------------------------------------------------------------------------
*/
public boolean add(E e) {
linkLast(e);
return true;
}
3. Vector
/*
一些参数--------------------------------------------------------------------------
*/
protected Object[] elementData;
protected int elementCount;
// 指定扩充时每次扩充多少,如果不指定则每次扩充时增长一倍
protected int capacityIncrement;
/*
构造方法--------------------------------------------------------------------------
*/
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
public Vector(int initialCapacity) {
this(initialCapacity, 0);
}
public Vector() {
this(10);
}
public Vector(Collection<? extends E> c) {
elementData = c.toArray();
elementCount = elementData.length;
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, elementCount, Object[].class);
}
/*
与扩充有关--------------------------------------------------------------------------
*/
public synchronized void ensureCapacity(int minCapacity) {
if (minCapacity > 0) {
modCount++;
ensureCapacityHelper(minCapacity);
}
}
private void ensureCapacityHelper(int minCapacity) {
if (minCapacity - elementData.length > 0)
grow(minCapacity);
}
private void grow(int minCapacity) {
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ? capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
/*
增加和删除元素--------------------------------------------------------------------------
*/
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
public void add(int index, E element) {
insertElementAt(element, index);
}
public synchronized boolean removeElement(Object obj) {
modCount++;
int i = indexOf(obj);
if (i >= 0) {
removeElementAt(i);
return true;
}
return false;
}
二维数组的创建
List<List<Integer>> triangle = new ArrayList<List<Integer>>();
triangle.add(new ArrayList<>());
triangle.get(0).add(1);
给ArrayList赋值
ArrayList<Character> list = new ArrayList<>(14); //创建一个ArrayList对象
Collections.addAll(list,'Z','z','X','x','C','c','V','v','B','b','N','n','M','m');
ArrayList和String[]互相转换
//不能为基本类型
// ArrayList转String[]
//1.
ArrayList list = new ArrayList();
String[] strs = (String[]) list.toArray(new String[]{});
//2.
List list = new ArrayList<>(Arrays.asList("a", "b", "c"))
//3.
Integer [] myArray = { 1, 2, 3 };
List myList = Arrays.stream(myArray).collect(Collectors.toList());
//4.
int [] myArray2 = { 1, 2, 3 };
List myList = Arrays.stream(myArray2).boxed().collect(Collectors.toList());
//String[]转ArrayList
String[] myArray = { "Apple", "Banana", "Orange" };
List<String> myList = Arrays.asList(myArray);
其他转换
Integer[] integersOne = new Integer[]{1, 2, 3, 4, 5};
ArrayList<Integer> listOne = new ArrayList() {{
add(1);
add(2);
add(3);
add(4);
add(5);
}};
int[] intsOne = {1, 2, 3, 4, 5};
// Integer[] 转 int[]
int[] intsTwo = Arrays.stream(integersOne).mapToInt(Integer::valueOf).toArray();
// ArrayList<Integer> 转 int[]
int[] intsThree = listOne.stream().mapToInt(Integer::valueOf).toArray();
// int[] 转 Integer[]
Integer[] integersTwo = (Integer[]) Arrays.stream(intsThree).mapToObj(Integer::valueOf).toArray();
// ArrayList<Integer> 转 Integer[]
Integer[] integersThree = (Integer[]) listOne.toArray();
// int[] 转 ArrayList<Integer>
List<Integer> listTwo = Arrays.stream(intsOne).boxed().collect(Collectors.toList());
//Integer[] 转 ArrayList<Integer>
List<Integer> listThree = Arrays.asList(integersOne);
比较器
//数组的比较器
private static class Node{
int a;
int b;
Node(int a, int b){
this.a = a;
this.b = b;
}
public String toString(){
return "[" + a + "," + b + "]";
}
}
Arrays.sort(nodes, Comparator.comparingInt(node -> node.b));
//容器的比较器
Collections.sort(arrayList, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2.compareTo(o1);
}
});
内部类
静态内部类和普通内部类的比较
public class Test() {
static class StaticInnerClass(){
}
class NormalInnerClass(){
}
public static void main(String[] args) {
//创建静态内部类的两种方法
Test.StaticInnerClass c1 = new Test.StaticInnerClass();
StaticInnerClass c2 = new StaticInnerClass();
//创建普通内部类的方法
Test.NormalInnerClass c3 = new Test().new NormalInnerClass();
}
简单内部类
//a iterator interface
public interface MyIterator {
boolean end();
Object current();
void next();
}
//Using iterator implement array traversal
public class Sequence {
private Object[] items;
private int next = 0;
public Sequence(int size){
items = new Object[size];
}
public void add(Object o) {
items[next++] = o;
}
public MyIterator iterator() {
return new MySequenceIterator();
}
private class MySequenceIterator implements MyIterator{
private int i = 0;
@Override
public boolean end() {
return i == next;
}
@Override
public Object current() {
return items[i];
}
//The Current function can't update the data after the return, so set this function.
@Override
public void next() {
i++;
}
}
public static void main(String[] args) {
Sequence seq = new Sequence(10);
for(int i = 0; i < 10; i++) {
seq.add(Integer.toString(i));
}
MyIterator it = seq.iterator();
while(!it.end()) {
System.out.println(it.current());
it.next();
}
}
}
JVM
对象的创建
- 当虚拟机遇到
new
指令时,虚拟机首先检查该指令的参数是否能在常量池中定位到一个符号引用,再检查符号引用对应的类是否被加载、解析和初始化过,如果没有,则先进行类的初始化过程。- 接下来将为新生对象分配内存(分配的内存大小在类加载之后便能够确定)。
- 虚拟机将分配到的内存都初始化为零值(除对象头外)。
- 对对象头进行初始化。
- 虚拟机的任务已经完成,接下来执行构造函数。
- 关于引用的一些约定:①根据引用可以直接或间接查找到对象在Java堆中的数据存放的起始地址或索引。②根据引用直接或间接地查找到对象所属数据类型在方法区中的存储的类型信息。
Class文件格式
常量池内各数据的引用关系
CONSTANT _ (Fieldref/Methodred) _ info -> CONSTANT _ class _ info -> CONSTANT _ Utf8 _ info
//上述Class文件反编译
Classfile /C:/Users/Dm20/eclipse-workspace/JVMTest/bin/org/fenixsoft/clazz/TestClass.class
Last modified 2020-2-12; size 393 bytes
MD5 checksum 36f9594ed938be052dd710eea2088c64
Compiled from "TestClass.java"
public class org.fenixsoft.clazz.TestClass
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Class #2 // org/fenixsoft/clazz/TestClass
#2 = Utf8 org/fenixsoft/clazz/TestClass
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 m
#6 = Utf8 I
#7 = Utf8 <init>
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Methodref #3.#11 // java/lang/Object."<init>":()V
#11 = NameAndType #7:#8 // "<init>":()V
#12 = Utf8 LineNumberTable
#13 = Utf8 LocalVariableTable
#14 = Utf8 this
#15 = Utf8 Lorg/fenixsoft/clazz/TestClass;
#16 = Utf8 inc
#17 = Utf8 ()I
#18 = Fieldref #1.#19 // org/fenixsoft/clazz/TestClass.m:I
#19 = NameAndType #5:#6 // m:I
#20 = Utf8 SourceFile
#21 = Utf8 TestClass.java
{
public org.fenixsoft.clazz.TestClass();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #10 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lorg/fenixsoft/clazz/TestClass;
public int inc();
descriptor: ()I
flags: ACC_PUBLIC
Code:
stack=2, locals=1, args_size=1
0: aload_0
1: getfield #18 // Field m:I
4: iconst_1
5: iadd
6: ireturn
LineNumberTable:
line 7: 0
LocalVariableTable:
Start Length Slot Name Signature
0 7 0 this Lorg/fenixsoft/clazz/TestClass;
}
SourceFile: "TestClass.java"
//new 指令的测试
Classfile /C:/Users/Dm20/eclipse-workspace/JVMTest/bin/org/fenixsoft/clazz/TestClass2.class
Last modified 2020-4-2; size 453 bytes
MD5 checksum 4d40af1aa5b5fac2d65fd8356da079fa
Compiled from "TestClass2.java"
public class org.fenixsoft.clazz.TestClass2
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Class #2 // org/fenixsoft/clazz/TestClass2
#2 = Utf8 org/fenixsoft/clazz/TestClass2
#3 = Class #4 // java/lang/Object
#4 = Utf8 java/lang/Object
#5 = Utf8 <init>
#6 = Utf8 ()V
#7 = Utf8 Code
#8 = Methodref #3.#9 // java/lang/Object."<init>":()V
#9 = NameAndType #5:#6 // "<init>":()V
#10 = Utf8 LineNumberTable
#11 = Utf8 LocalVariableTable
#12 = Utf8 this
#13 = Utf8 Lorg/fenixsoft/clazz/TestClass2;
#14 = Utf8 main
#15 = Utf8 ([Ljava/lang/String;)V
#16 = Methodref #1.#9 // org/fenixsoft/clazz/TestClass2."<init>":()V
#17 = Utf8 args
#18 = Utf8 [Ljava/lang/String;
#19 = Utf8 testClass2
#20 = Utf8 SourceFile
#21 = Utf8 TestClass2.java
{
public org.fenixsoft.clazz.TestClass2();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: invokespecial #8 // Method java/lang/Object."<init>":()V
4: return
LineNumberTable:
line 3: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lorg/fenixsoft/clazz/TestClass2;
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #1 // class org/fenixsoft/clazz/TestClass2
3: dup
4: invokespecial #16 // Method "<init>":()V
7: astore_1
8: return
LineNumberTable:
line 5: 0
line 6: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
8 1 1 testClass2 Lorg/fenixsoft/clazz/TestClass2;
}
SourceFile: "TestClass2.java"
类加载过程
加载
通过类的全限定名获取定义此类的二进制文件(.class文件)。
将二进制文件转化为方法区的运行时数据结构。
在内存中生成代表这个类的Class对象,作为方法区这个类的各种类的访问入口。
验证
- 文件格式验证
- 元数据验证
- 字节码验证
- 符号引用验证
准备
- 为类变量分配内存并初始化
解析
- 将常量池中的符号引用 (用符号来描述所引用的目标) 替换为直接引用 (直接指向所引用的目标) 的过程。
- 类或接口的解析 :用B类的类加载器通过全限定名加载A类。先进行类解析,在进行下列解析。
- 字段解析
- 方法解析
- 接口方法解析
初始化
- 执行类构造器()方法的过程。其中方法由编译器自动收集类中所有类变量的赋值动作和静态语句块中的语句合并产生的。
Java8新特性
Lambda表达式(因为可以由编译器推断出来)
-
无参数无返回值
Runnable runnable = () -> {System.out.println("Hello world");} Runnable runnable = () -> System.out.println("Hello world"); //只有一条语句时可以省略大括号
-
一个参数无返回值
Consumer<String> consumer = (str) -> {System.out.println("Hello world");} Consumer<String> consumer = (str) -> System.out.println("Hello world"); //只有一条语句时可以省略大括号
-
有返回值
Comparator<Integer> comparator = (x, y) -> { System.out.println("实现函数式接口方法"); Integer.compare(x, y); } Comparator<Integer> comparator = (x, y) -> Integer.compare(x, y); //只有一条语句时可以省略大括号
方法引用
- 对象::实例方法名
- 类::静态方法名
- 类::实例方法名
流式编程
多线程
Java中的锁
线程安全
当多个线程同时访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者在调用方法进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那就称这个对象是线程安全的。
状态
函数 | 所在类 | 作用 | 调用者 |
---|---|---|---|
wait() | Object.normal | 当一个线程调用一个共享变量的wait()方法时,该调用线程会被阻塞挂起,直到发生下面几件事情之一才返回:(1)其他线程调用了该共享对象的notify()或者notifyAll()方法; (2)其他线程调用了该线程的interrupt()方法,该线程抛出InterruptedException异常返回。 | 锁 |
notify() | Object.native | 一个线程调用共享对象的notify()方法后,会唤醒一个在该共享变量上调用wait系列方法后被挂起的线程。一个共享变量上可能会有多个线程在等待,具体唤醒哪个等待的线程是随机的。 | 锁 |
notifyAll() | Object.native | notifyAll()方法则会唤醒所有在该共享变量上由于调用wait系列方法而被挂起的线程。 | 锁 |
sleep() | Thread.static.native | 当一个执行中的线程调用了Thread的sleep方法后,调用线程会暂时让出指定时间的执行权,也就是在这期间不参与CPU的调度,但是该线程所拥有的监视器资源,比如锁还是持有不让出的。指定的睡眠时间到了后该函数会正常返回,线程就处于就绪状态,然后参与CPU的调度,获取到CPU资源后就可以继续运行了。如果在睡眠期间其他线程调用了该线程的interrupt()方法中断了该线程,则该线程会在调用sleep方法的地方抛出InterruptedException异常而返回。 | |
yield() | Thread.native | Thread类中有一个静态的yield方法,当一个线程调用yield方法时,实际就是在暗示线程调度器当前线程请求让出自己的CPU使用,但是线程调度器可以无条件忽略这个暗示。 | |
join() | Thread.normal | 让队,让调用该方法线程先运行。 | 线程 |
interrupt() | Thread.normal | 当线程A运行时,线程B可以调用线程A的interrupt()方法来设置线程A的中断标志为true并立即返回。设置标志仅仅是设置标志,线程A实际并没有被中断,它会继续往下执行。 | |
interrupted() | Thread.static | 检测当前线程是否被中断,如果是返回true,否则返回false。与isInterrupted不同的是,该方法如果发现当前线程被中断,则会清除中断标志,并且该方法是static方法,可以通过Thread类直接调用。 | |
isInterrupted() | Thread.normal | 检测当前线程是否被中断,如果是返回true,否则返回false。 |
名称 | 作用 |
---|---|
悲观锁 | 对外界修改保持悲观态度,认为数据很容易被其他线程修改,所以在数据被处理前先对数据进行加锁,并在整个数据的处理过程中使数据处于锁定状态。 |
乐观锁 | 认为数据在一般情况下不会造成冲突,所以在访问数据前不会加排它锁,而是在进行数据提交更新时,才会正式对数据冲突与否进行检测。 |
公平锁 | 线程获取的锁的顺序是按照先来后到。 |
非公平锁 | 先来不一定先得。 |
独享锁 | 保证任何时候都只有一个线程能得到锁。 |
共享锁 | 运行多个线程同时进行读操作,即多个线程共用一把锁。 |
可重入锁 | 能够重复获得自己已经获得的锁,而不会被阻塞。 |
自旋锁 | 在获取锁失败时,不会马上进入阻塞,多尝试几次(默认10次)。 |
排它锁 | 当一个线程获取这个锁后,其他线程必须等待该线程释放锁后才能获取该锁。 |
内置锁 | |
显式锁 | Lock提供了一种无条件的、可轮询的、定时的以及可中断的锁获取操作,所有加锁和解锁的方法都是显式的。 |
术语和关键字
-
volatile
关键字volatile
用来修饰字段,就是告知程序对该变量的访问均需要从共享内存中获取,而对它的改变必须同步刷回内存,它能保证所有现场对变量访问的可见性。 -
synchronized
- 对于普通同步方法,锁是当前实例对象。
- 对于静态同步方法,锁是当前类的Class对象。
- 对于同步方法快,锁是Synchronized括号里配置的对象。
同步代码块是使用monitorenter和monitorexit实现的。关键字
synchronized
确保了在同一时刻只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。 -
wait常与s nchronized一起使用,并且调用
wait()
方法会释放且只会释放当前共享变量的锁,并且在wait()
后调用interrupt()
会抛出InterruptedException异常// 生产者线程 synchronized(queue) { // 一直检测队列是否为满,如果为满,则调用 wait 方法, 防止虚假唤起 while(queue.size() == MAX_SIZE) { try { queue.wait(); } catch(Exception e) { e.printStackTrace(); } } // 如果空闲则生成元素,并通知消费者线程过来消费数据 queue.add(element); queue.notifyAll(); } // 消费者线程 synchronized(queue) { // 如果队列为空,则调用 wait while(queue.size() == 0) { try { queue.wait(); } catch(Exception e) { e.printStackTrace(); } } // 消费者消费数据,并通知生产者生产数据 queue.remove(); queue.notifyAll(); }
-
notify()
package concurrent; public class TestNotifyAll { private static volatile Object aLock = new Object(); public static void main(String[] args) throws InterruptedException { Thread threadA = new Thread(() ->{ synchronized (aLock) { System.out.println("threadA get lock"); try { System.out.println("threadA begin wait"); aLock.wait(); System.out.println("threadA end wait"); } catch (InterruptedException e) { e.printStackTrace(); } } }); Thread threadB = new Thread(() -> { synchronized (aLock) { System.out.println("threadB get lock"); try { System.out.println("threadB begin wait"); aLock.wait(); System.out.println("threadB end wait"); } catch (InterruptedException e) { e.printStackTrace(); } } }); // threadC调用notifyAll,因为threadC持有锁,所以可以调用 Thread threadC = new Thread(() -> { synchronized (aLock) { System.out.println("threadC begin notifyAll"); aLock.notifyAll(); } }); threadA.start(); threadB.start(); Thread.sleep(1000); threadC.start(); // 三个线程插队 threadA.join(); threadB.join(); threadC.join(); System.out.println("main over"); } }
-
happens-before
如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系。 -
as-if-serial
不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。编译期、runtime和处理器都必须遵守as-if-serial语义。 -
内存屏障
一组处理器指令,用于实现对内存顺序限制。 -
等待/通知模型
等待方:- 获取对象的锁
- 如果条件不满足,那么调用对象的
wait()
方法,被通知后仍要检查条件。 - 条件满足则执行对应的逻辑。
对应的伪代码为:
synchronized(对象) { while(条件不满足) { 对象.wait(); } 对应的处理逻辑 }
通知方:
- 获取对象的锁
- 改变条件
- 通知所有等待在对象上的线程。
对应的伪代码为:
synchronized(对象) { 改变条件 对象.notifyAll(); }
-
Thread.join()
当前线程A等待thread线程终止之后 -
Lock
接口提供的而synchronized
关键字不具备的特性- 常识非阻塞地获取锁:当前线程尝试获取锁,如果这一时刻没有被其他线程获取到,则成功获取并持有锁。
- 能被中断地获取锁:与
synchronized
不同,获取到锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁会被释放 - 超时获取锁:在指定的截止时间之间获取锁,如果截止时间到了仍旧无法获取锁,则返回。
-
CountDownLatch
package slqx.CountDownLatch; import java.util.concurrent.CountDownLatch; public class CountDownLatchTest { static CountDownLatch countDownLatch = new CountDownLatch(2); public static void main(String[] args) throws InterruptedException { new Thread(() -> { System.out.println("parser1 finished!"); countDownLatch.countDown(); }).start(); new Thread(() -> { System.out.println("parser2 finished!"); countDownLatch.countDown(); }).start(); // 等待 countDownLatch的值为0 countDownLatch.await(); System.out.println("all parser finish!"); } }
-
CyclicBarrier
package slqx.CyclicBarrier; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; public class CyclicBarrierTest { // 当前用例有两个线程,一个主线程,一个自己创建的线程,当所有的线程都调用await() 方法时,线程才会继续往下执行,否则会阻塞在await()方法处 static CyclicBarrier cyclicBarrier = new CyclicBarrier(2); public static void main(String[] args) { new Thread(() -> { try { cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } System.out.println("parser1 finished!"); }).start(); try { cyclicBarrier.await(); } catch (InterruptedException e) { e.printStackTrace(); } catch (BrokenBarrierException e) { e.printStackTrace(); } System.out.println("parser2 finished!"); } }
重要
-
Java中的并发采用的是共享内存模型,Java线程之间的通信总是隐式进行,整个通信过程对程序员完全透明。
-
wait()
和notify()
都是通过共享变量去调用的,并且只有当前线程获取到了共享变量的监视器锁之后才可以调用共享变量的nofity()
方法,否则会抛出IllegalMonitorStateException异常。 -
JMM属于语言级别的内存模型,它确保了在不同的编译期和不同的处理器平台之上,通过禁止特定类型的编译期重排序和处理器重排序,为程序员提供一致的内存可见性保证。
-
Java线程之间的通信由JMM控制,JMM决定一个线程对共享变量的写入何时对另一个线程可见。从抽象的角度来看,JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存中,每个线程都有一个私有的本地内存,本地内存中存储了该线程以读/写共享变量的副本。本地内存是JMM的一个抽象概念,并不真实存在。
-
停止线程的两种方式
public class Shundown { private static class Runner implements Runnable { private long i; private volatile boolean on = true; @Override public void run() { while(on && !Thread.currentThread().isInterrupted()) { i++; } System.out.println("当前i的值为:" + i); } private void cancel() { on = false; } } public static void main(String[] args) throw Exception { Runner one = new Runner(); Thread countThread = new Thread(one, "countThread"); countThread.start(); Thread.sleep(1000); // 通过 interrupt方法中断线程 countThread.interrupt(); Runner two = new Runner(); countThread = new Thread(two, "countThread"); countThread.start(); two.cancel(); } }
小知识
求模
System.out.println((17) % (10)); //result = 7
System.out.println((17) % (-10)); //result = 7
System.out.println((-17) % (10)); //result = -7
System.out.println((-17) % (-10)); //result = -7
由此可见,求模的结果向0靠近
正数求模求得的结果为正数
负数求模求得的结果为负数
Spring
概念
设计思路与作用
践行工厂模式,打造一个工厂,通过工厂完成对项目的管理。
Spring 的主要作用就是为代码“解耦”,降低代码间的耦合度。
根据功能的不同,可以将一个系统中的代码分为 主业务逻辑 与 系统级业务逻辑 两类。它们各自具有鲜明的特点:主业务代码间逻辑联系紧密,有具体的专业业务应用场景,复用性相对较低;系统级业务相对功能独立,没有具体的专业业务应用场景,主要是为主业务提供系统级服务,如日志、安全、事务等,复用性强。
Spring 根据代码的功能特点,将降低耦合度的方式分为了两类:IoC 与 AOP。IoC 使得主业务在相互调用过程中,不用再自己维护关系了,即不用再自己创建要使用的对象了。而是由 Spring 容器统一管理,自动“注入”。而 AOP 使得系统级服务得到了最大复用,且不用再由程序员手工将系统级服务“混杂”到主业务逻辑中了,而是由 Spring 容器统一完成“织入”。
Spring 是于 2003 年兴起的一个轻量级的 Java 开发框架,它是为了解决企业应用开发的复杂性而创建的。Spring 的核心是控制反转(IoC)和面向切面编程(AOP)。简单来说,Spring 是一个分层的 Java SE/EE full-stack(一站式)轻量级开源框架。
分层(分层结构)
- 表现层(页面数据显示、页面跳转调度, View),例如 JSP、Servlet
- 业务层(业务逻辑和功能逻辑、事务控制, Service),例如 Service
- 持久层(数据存取和封装、和数据库打交道, DAO),例如DAO
MVC
概念
- View:视图,为用户提供使用界面,与用户直接进行交互。
- Model:模型,承载数据,并对用户提交请求进行计算的模块。其分为两类,一类称为数据承载 Bean,一类称为业务处理 Bean。所谓数据承载 Bean 是指实体类,专门用户承载业务数据的,如 Student、User 等。而业务处理 Bean 则是指 Service 或 Dao对象, 专门用于处理用户提交请求的。
- Controller:控制器,用于将用户请求转发给相应的 Model 进行处理,并根据 Model 的计算结果向用户提供相应响应。
工作流程
- 用户通过 View 页面向服务端提出请求,可以是表单请求、超链接请求、AJAX 请求等
- 服务端 Controller 控制器接收到请求后对请求进行解析,找到相应的 Model 对用户请求进行处理
- Model 处理后,将处理结果再交给 Controller
- Controller 在接到处理结果后,根据处理结果找到要作为向客户端发回的响应 View 页面。页面经渲染(数据填充)后,再发送给客户端。
Spring Framework的结构
重要术语
- IoC : 将对象的创建权利交给Spring工厂进行管理。通过DI完成,即容器在对象初始化时不等对象请求就主动将依赖传递给它。
- AOP : 我们可以把日志、安全、事务管理等服务理解成一个“切面”,那么以前这些服务一直是直接写在业务逻辑的代码当中的,这有两点不好:首先业务逻辑不纯净;其次这些服务被很多业务逻辑反复使用,完全可以剥离出来做到复用。那么 AOP 就是这些问题的解决方案, 可以把这些服务剥离出来形成一个“切面”,以期复用,然后将“切面”动态的“织入”到业务逻辑中,让业务逻辑能够享受到此“切面”的服务。
- DI:依赖注入,对象和对象之间的依赖关系的创建。
- bean : Spring工厂通过反射机制生成的一个对象。
- class:类的全限定名。
- id / name:表示对象的名字,用来获取bean对象的表示,一般用接口的名字(小驼峰命名)
优点
- 方便解耦,简化开发:Spring就是一个大工程,负责对象创建和依赖关系维护。
- AOP编程的支持:Spring提供面向切面编程,可以方便的实现对程序进行权限拦截、运行监控等功能。
- 声明式事务的支持:只需要通过配置就可以完成对事务的控制
- 方便程序的测试:Spring对Junit4的支持,使得可以通过注解方便的测试Spring程序。
- 支持多种框架:Spring内部提供了对多种框架的支持。
- 降低API的使用难度:Spring对一些常用的API都提供了封装。
IDE
快捷键
查看类的继承关系
ctrl + shift + alt + u
光标移到下一个报错处
F2
查看接口的实现类
ctrl + b
ctrl + shift + b
ctrl + alt + 鼠标左键
查看类的继承关系
ctrl + H
DEBUG
- step over :不进入方法体
- step into:进入方法体,但不会进入类库方法
- force step into:会进入方法体,和类库方法
- step out:退出当前方法
- drop Frame:返回当前方法调用处,以便于重新进行调试
- run to cursor:运行到光标处
- evaluate Expression:计算表达式
- resume program:跳到下一个断点
- mute breakpoints:断点哑音
- 在Variables框中 右键->new watch
- 在断点处右键->设置条件断点,再点击Resume Program
- 在断点处右键->选中Thread->继续Debug->下拉框选中线程