Android学习指南 — Java知识点汇总,2024年最新面试被吊打

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip1024b (备注Java)
img

正文

| 常见的检查性异常 | | |

| IOException | CloneNotSupportedException | IllegalAccessException |

| NoSuchFieldException | NoSuchMethodException | FileNotFoundException |

内部类

===

  • 非静态内部类没法在外部类的静态方法中实例化。

  • 非静态内部类的方法可以直接访问外部类的所有数据,包括私有的数据。

  • 在静态内部类中调用外部类成员,成员也要求用 static 修饰。

  • 创建静态内部类的对象可以直接通过外部类调用静态内部类的构造器;创建非静态的内部类的对象必须先创建外部类的对象,通过外部类的对象调用内部类的构造器。

匿名内部类


  • 匿名内部类不能定义任何静态成员、方法

  • 匿名内部类中的方法不能是抽象的

  • 匿名内部类必须实现接口或抽象父类的所有抽象方法

  • 匿名内部类不能定义构造器

  • 匿名内部类访问的外部类成员变量或成员方法必须用 final 修饰

多态

==

  • 父类的引用可以指向子类的对象

  • 创建子类对象时,调用的方法为子类重写的方法或者继承的方法

  • 如果我们在子类中编写一个独有的方法,此时就不能通过父类的引用创建的子类对象来调用该方法

抽象和接口

=====

  • 抽象类不能有对象(不能用 new 关键字来创建抽象类的对象)

  • 抽象类中的抽象方法必须在子类中被重写

  • 接口中的所有属性默认为:public static final;

  • 接口中的所有方法默认为:public abstract;

集合框架

====

  • List接口存储一组不唯一,有序(插入顺序)的对象, Set接口存储一组唯一,无序的对象。

HashMap


结构图

  • JDK 1.7 HashMap 结构图

  • JDK 1.8 HashMap 结构图

HashMap 的工作原理

HashMap 基于 hashing 原理,我们通过 put() 和 get() 方法储存和获取对象。当我们将键值对传递给 put() 方法时,它调用键对象的 hashCode() 方法来计算 hashcode,让后找到 bucket 位置来储存 Entry 对象。当两个对象的 hashcode 相同时,它们的 bucket 位置相同,‘碰撞’会发生。因为 HashMap 使用链表存储对象,这个 Entry 会存储在链表中,当获取对象时,通过键对象的 equals() 方法找到正确的键值对,然后返回值对象。

如果 HashMap 的大小超过了负载因子(load factor)定义的容量,怎么办?

默认的负载因子大小为 0.75,也就是说,当一个 map 填满了 75% 的 bucket 时候,和其它集合类(如 ArrayList 等)一样,将会创建原来 HashMap 大小的两倍的 bucket 数组,来重新调整 map 的大小,并将原来的对象放入新的 bucket 数组中。这个过程叫作 rehashing,因为它调用 hash 方法找到新的 bucket 位置。

为什么 String, Interger 这样的 wrapper 类适合作为键?

因为 String 是不可变的,也是 final 的,而且已经重写了 equals() 和 hashCode() 方法了。其他的 wrapper 类也有这个特点。不可变性是必要的,因为为了要计算 hashCode(),就要防止键值改变,如果键值在放入时和获取时返回不同的 hashcode 的话,那么就不能从 HashMap 中找到你想要的对象。不可变性还有其他的优点如线程安全。如果你可以仅仅通过将某个 field 声明成 final 就能保证 hashCode 是不变的,那么请这么做吧。因为获取对象的时候要用到 equals() 和 hashCode() 方法,那么键对象正确的重写这两个方法是非常重要的。如果两个不相等的对象返回不同的 hashcode 的话,那么碰撞的几率就会小些,这样就能提高 HashMap 的性能。

HashMap 与 HashTable 对比

HashMap 是非 synchronized 的,性能更好,HashMap 可以接受为 null 的 key-value,而 Hashtable 是线程安全的,比 HashMap 要慢,不接受 null 的 key-value。

HashMap.java

public class HashMap<K,V> extends AbstractMap<K,V>

implements Map<K,V>, Cloneable, Serializable {

···

public V put(K key, V value) {

return putVal(hash(key), key, value, false, true);

}

···

public V get(Object key) {

Node<K,V> e;

return (e = getNode(hash(key), key)) == null ? null : e.value;

}

···

}

HashTable.java

public class Hashtable<K,V>

extends Dictionary<K,V>

implements Map<K,V>, Cloneable, java.io.Serializable {

···

public synchronized V put(K key, V value) {

// Make sure the value is not null

if (value == null) {

throw new NullPointerException();

}

···

addEntry(hash, key, value, index);

return null;

}

···

public synchronized V get(Object key) {

HashtableEntry<?,?> tab[] = table;

int hash = key.hashCode();

int index = (hash & 0x7FFFFFFF) % tab.length;

for (HashtableEntry<?,?> e = tab[index] ; e != null ; e = e.next) {

if ((e.hash == hash) && e.key.equals(key)) {

return (V)e.value;

}

}

return null;

}

···

}

ConcurrentHashMap


Base 1.7

ConcurrentHashMap 最外层不是一个大的数组,而是一个 Segment 的数组。每个 Segment 包含一个与 HashMap 数据结构差不多的链表数组。

正在上传…重新上传取消​

在读写某个 Key 时,先取该 Key 的哈希值。并将哈希值的高 N 位对 Segment 个数取模从而得到该 Key 应该属于哪个Segment,接着如同操作 HashMap 一样操作这个 Segment。

Segment 继承自 ReentrantLock,可以很方便的对每一个 Segmen 上锁。

对于读操作,获取 Key 所在的 Segment 时,需要保证可见性。具体实现上可以使用volatile关键字,也可使用锁。但使用锁开销太大,而使用volatile时每次写操作都会让所有CPU内缓存无效,也有一定开销。ConcurrentHashMap 使用如下方法保证可见性,取得最新的Segment:

Segment<K,V> s = (Segment<K,V>)UNSAFE.getObjectVolatile(segments, u)

获取 Segment 中的 HashEntry 时也使用了类似方法:

HashEntry<K,V> e = (HashEntry<K,V>) UNSAFE.getObjectVolatile

(tab, ((long)(((tab.length - 1) & h)) << TSHIFT) + TBASE)

对于写操作,并不要求同时获取所有 Segment 的锁,因为那样相当于锁住了整个Map。它会先获取该 Key-Value 对所在的 Segment 的锁,获取成功后就可以像操作一个普通的 HashMap 一样操作该 Segment,并保证该 Segment 的安全性。同时由于其它 Segment 的锁并未被获取,因此理论上可支持 concurrencyLevel(等于Segment的个数)个线程安全的并发读写。

获取锁时,并不直接使用 lock 来获取,因为该方法获取锁失败时会挂起。事实上,它使用了自旋锁,如果 tryLock 获取锁失败,说明锁被其它线程占用,此时通过循环再次以 tryLock 的方式申请锁。如果在循环过程中该 Key 所对应的链表头被修改,则重置 retry 次数。如果 retry 次数超过一定值,则使用 lock 方法申请锁。

这里使用自旋锁是因为自旋锁的效率比较高,但是它消耗 CPU 资源比较多,因此在自旋次数超过阈值时切换为互斥锁。

Base 1.8

1.7 已经解决了并发问题,并且能支持 N 个 Segment 这么多次数的并发,但依然存在 HashMap 在 1.7 版本中的问题:查询遍历链表效率太低。因此 1.8 做了一些数据结构上的调整。

其中抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性。

ConcurrentHashMap.java

final V putVal(K key, V value, boolean onlyIfAbsent) {

if (key == null || value == null) throw new NullPointerException();

int hash = spread(key.hashCode());

int binCount = 0;

for (Node<K,V>[] tab = table;😉 {

Node<K,V> f; int n, i, fh;

if (tab == null || (n = tab.length) == 0)

tab = initTable();

else if ((f = tabAt(tab, i = (n - 1) & hash)) == null) {

if (casTabAt(tab, i, null,

new Node<K,V>(hash, key, value, null)))

break; // no lock when adding to empty bin

}

else if ((fh = f.hash) == MOVED)

tab = helpTransfer(tab, f);

else {

V oldVal = null;

synchronized (f) {

if (tabAt(tab, i) == f) {

if (fh >= 0) {

binCount = 1;

···

}

else if (f instanceof TreeBin) {

···

}

else if (f instanceof ReservationNode)

throw new IllegalStateException(“Recursive update”);

}

}

···

}

addCount(1L, binCount);

return null;

}

ArrayList


ArrayList 本质上是一个动态数组,第一次添加元素时,数组大小将变化为 DEFAULT_CAPACITY 10,不断添加元素后,会进行扩容。删除元素时,会按照位置关系把数组元素整体(复制)移动一遍。 ArrayList.java

public class ArrayList extends AbstractList

implements List, RandomAccess, Cloneable, java.io.Serializable

···

// 增加元素

public boolean add(E e) {

ensureCapacityInternal(size + 1); // Increments modCount!!

elementData[size++] = e;

return true;

}

···

// 删除元素

public E remove(int index) {

if (index >= size)

throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

modCount++;

E oldValue = (E) elementData[index];

int numMoved = size - index - 1;

if (numMoved > 0)

System.arraycopy(elementData, index+1, elementData, index,

numMoved);

elementData[–size] = null; // clear to let GC do its work

return oldValue;

}

···

// 查找元素

public E get(int index) {

if (index >= size)

throw new IndexOutOfBoundsException(outOfBoundsMsg(index));

return (E) elementData[index];

}

···

}

LinkedList


LinkedList 本质上是一个双向链表的存储结构。

LinkedList.java

public class LinkedList

extends AbstractSequentialList

implements List, Deque, Cloneable, java.io.Serializable

{

····

private static class Node {

E item;

Node next;

Node prev;

Node(Node prev, E element, Node next) {

this.item = element;

this.next = next;

this.prev = prev;

}

}

···

// 增加元素

void linkLast(E e) {

final Node l = last;

final Node newNode = new Node<>(l, e, null);

last = newNode;

if (l == null)

first = newNode;

else

l.next = newNode;

size++;

modCount++;

}

···

// 删除元素

E unlink(Node x) {

final E element = x.item;

final Node next = x.next;

final Node 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;

}

···

// 查找元素

Node node(int index) {

// assert isElementIndex(index);

if (index < (size >> 1)) {

Node x = first;

for (int i = 0; i < index; i++)

x = x.next;

return x;

} else {

Node x = last;

for (int i = size - 1; i > index; i–)

x = x.prev;

return x;

}

}

···

}

对于元素查询来说,ArrayList 优于 LinkedList,因为 LinkedList 要移动指针。对于新增和删除操作,LinedList 比较占优势,因为 ArrayList 要移动数据。

CopyOnWriteArrayList


CopyOnWriteArrayList 是线程安全容器(相对于 ArrayList),增加删除等写操作通过加锁的形式保证数据一致性,通过复制新集合的方式解决遍历迭代的问题。

CopyOnWriteArrayList.java

public class CopyOnWriteArrayList

implements List, RandomAccess, Cloneable, java.io.Serializable {

final transient Object lock = new Object();

···

// 增加元素

public boolean add(E e) {

synchronized (lock) {

Object[] elements = getArray();

int len = elements.length;

Object[] newElements = Arrays.copyOf(elements, len + 1);

newElements[len] = e;

setArray(newElements);

return true;

}

}

···

// 删除元素

public E remove(int index) {

synchronized (lock) {

Object[] elements = getArray();

int len = elements.length;

E oldValue = get(elements, index);

int numMoved = len - index - 1;

if (numMoved == 0)

setArray(Arrays.copyOf(elements, len - 1));

else {

Object[] newElements = new Object[len - 1];

System.arraycopy(elements, 0, newElements, 0, index);

System.arraycopy(elements, index + 1, newElements, index,

numMoved);

setArray(newElements);

}

return oldValue;

}

}

···

// 查找元素

private E get(Object[] a, int index) {

return (E) a[index];

}

}

反射

==

try {

Class cls = Class.forName(“com.jasonwu.Test”);

//获取构造方法

Constructor[] publicConstructors = cls.getConstructors();

//获取全部构造方法

Constructor[] declaredConstructors = cls.getDeclaredConstructors();

//获取公开方法

Method[] methods = cls.getMethods();

//获取全部方法

Method[] declaredMethods = cls.getDeclaredMethods();

//获取公开属性

Field[] publicFields = cls.getFields();

//获取全部属性

Field[] declaredFields = cls.getDeclaredFields();

Object clsObject = cls.newInstance();

Method method = cls.getDeclaredMethod(“getModule1Functionality”);

Object object = method.invoke(null);

} catch (ClassNotFoundException e) {

e.printStackTrace();

} catch (IllegalAccessException e) {

e.printStackTrace();

} catch (InstantiationException e) {

e.printStackTrace();

} catch (NoSuchMethodException e) {

e.printStackTrace();

} catch (InvocationTargetException e) {

e.printStackTrace();

}

单例

==

饿汉式


public class CustomManager {

private Context mContext;

private static final Object mLock = new Object();

private static CustomManager mInstance;

public static CustomManager getInstance(Context context) {

synchronized (mLock) {

if (mInstance == null) {

mInstance = new CustomManager(context);

}

return mInstance;

}

}

private CustomManager(Context context) {

this.mContext = context.getApplicationContext();

}

}

双重检查模式


public class CustomManager {

private Context mContext;

private volatile static CustomManager mInstance;

public static CustomManager getInstance(Context context) {

// 避免非必要加锁

if (mInstance == null) {

synchronized (CustomManger.class) {

if (mInstance == null) {

mInstacne = new CustomManager(context);

}

}

}

return mInstacne;

}

private CustomManager(Context context) {

this.mContext = context.getApplicationContext();

}

}

静态内部类模式


public class CustomManager{

private CustomManager(){}

private static class CustomManagerHolder {

private static final CustomManager INSTANCE = new CustomManager();

}

public static CustomManager getInstance() {

return CustomManagerHolder.INSTANCE;

}

}

静态内部类的原理是: 当 SingleTon 第一次被加载时,并不需要去加载 SingleTonHoler,只有当 getInstance() 方法第一次被调用时,才会去初始化 INSTANCE,这种方法不仅能确保线程安全,也能保证单例的唯一性,同时也延迟了单例的实例化。getInstance 方法并没有多次去 new 对象,取的都是同一个 INSTANCE 对象。

虚拟机会保证一个类的 <clinit>() 方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的 <clinit>() 方法,其他线程都需要阻塞等待,直到活动线程执行 <clinit>() 方法完毕

缺点在于无法传递参数,如Context等

线程

==

线程是进程中可独立执行的最小单位,也是 CPU 资源(时间片)分配的基本单位。同一个进程中的线程可以共享进程中的资源,如内存空间和文件句柄。

属性

| 属性 | 说明 |

| — | — |

| id | 线程 id 用于标识不同的线程。编号可能被后续创建的线程使用。编号是只读属性,不能修改 |

| name | 名字的默认值是 Thread-(id) |

| daemon | 分为守护线程和用户线程,我们可以通过 setDaemon(true) 把线程设置为守护线程。守护线程通常用于执行不重要的任务,比如监控其他线程的运行情况,GC 线程就是一个守护线程。setDaemon() 要在线程启动前设置,否则 JVM 会抛出非法线程状态异常,可被继承。 |

| priority | 线程调度器会根据这个值来决定优先运行哪个线程(不保证),优先级的取值范围为 1~10,默认值是 5,可被继承。Thread 中定义了下面三个优先级常量: - 最低优先级:MIN_PRIORITY = 1 - 默认优先级:NORM_PRIORITY = 5 - 最高优先级:MAX_PRIORITY = 10 |

状态

正在上传…重新上传取消​

| 状态 | 说明 |

| — | — |

| New | 新创建了一个线程对象,但还没有调用start()方法。 |

| Runnable | Ready 状态 线程对象创建后,其他线程(比如 main 线程)调用了该对象的 start() 方法。该状态的线程位于可运行线程池中,等待被线程调度选中 获取 cpu 的使用权。Running 绪状态的线程在获得 CPU 时间片后变为运行中状态(running)。 |

| Blocked | 线程因为某种原因放弃了cpu 使用权(等待锁),暂时停止运行 |

| Waiting | 线程进入等待状态因为以下几个方法: - Object#wait() - Thread#join() - LockSupport#park() |

| Timed Waiting | 有等待时间的等待状态。 |

| Terminated | 表示该线程已经执行完毕。 |

状态控制


  • wait() / notify() / notifyAll()

wait()notify()notifyAll() 是定义在Object类的实例方法,用于控制线程状态,三个方法都必须在synchronized 同步关键字所限定的作用域中调用,否则会报错 java.lang.IllegalMonitorStateException

| 方法 | 说明 |

| — | — |

| wait() | 线程状态由 的使用权。Running 变为 Waiting, 并将当前线程放入等待队列中 |

| notify() | notify() 方法是将等待队列中一个等待线程从等待队列移动到同步队列中 |

| notifyAll() | 则是将所有等待队列中的线程移动到同步队列中 |

被移动的线程状态由 Running 变为 Blocked,notifyAll 方法调用后,等待线程依旧不会从 wait() 返回,需要调用 notify() 或者 notifyAll() 的线程释放掉锁后,等待线程才有机会从 wait() 返回。

  • join() / sleep() / yield()

在很多情况,主线程创建并启动子线程,如果子线程中需要进行大量的耗时计算,主线程往往早于子线程结束。这时,如果主线程想等待子线程执行结束之后再结束,比如子线程处理一个数据,主线程要取得这个数据,就要用 join() 方法。

sleep(long) 方法在睡眠时不释放对象锁,而 join() 方法在等待的过程中释放对象锁。

yield() 方法会临时暂停当前正在执行的线程,来让有同样优先级的正在等待的线程有机会执行。如果没有正在等待的线程,或者所有正在等待的线程的优先级都比较低,那么该线程会继续运行。执行了yield方法的线程什么时候会继续运行由线程调度器来决定。

volatile

========

当把变量声明为 volatile 类型后,编译器与运行时都会注意到这个变量是共享的,因此不会将该变量上的操作与其他内存操作一起重排序。volatile 变量不会被缓存在寄存器或者对其他处理器不可见的地方,JVM 保证了每次读变量都从内存中读,跳过 CPU cache 这一步,因此在读取 volatile 类型的变量时总会返回最新写入的值。

当一个变量定义为 volatile 之后,将具备以下特性:

  • 保证此变量对所有的线程的可见性,不能保证它具有原子性(可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的)

  • 禁止指令重排序优化

  • volatile 的读性能消耗与普通变量几乎相同,但是写操作稍慢,因为它需要在本地代码中插入许多内存屏障指令来保证处理器不发生乱序执行

AtomicInteger 中主要实现了整型的原子操作,防止并发情况下出现异常结果,其内部主要依靠 JDK 中的 unsafe 类操作内存中的数据来实现的。volatile 修饰符保证了 value 在内存中其他线程可以看到其值得改变。CAS(Compare and Swap)操作保证了 AtomicInteger 可以安全的修改value 的值。

synchronized

============

当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码。

在 Java 中,每个对象都会有一个 monitor 对象,这个对象其实就是 Java 对象的锁,通常会被称为“内置锁”或“对象锁”。类的对象可以有多个,所以每个对象有其独立的对象锁,互不干扰。针对每个类也有一个锁,可以称为“类锁”,类锁实际上是通过对象锁实现的,即类的 Class 对象锁。每个类只有一个 Class 对象,所以每个类只有一个类锁。

Monitor 是线程私有的数据结构,每一个线程都有一个可用 monitor record 列表,同时还有一个全局的可用列表。每一个被锁住的对象都会和一个 monitor 关联,同时 monitor 中有一个 Owner 字段存放拥有该锁的线程的唯一标识,表示该锁被这个线程占用。Monitor 是依赖于底层的操作系统的 Mutex Lock(互斥锁)来实现的线程同步。

根据获取的锁分类


获取对象锁

  • synchronized(this|object) {}

  • 修饰非静态方法

获取类锁

  • synchronized(类.class) {}

  • 修饰静态方法

原理

同步代码块:

  • monitorenter 和 monitorexit 指令实现的

同步方法

  • 方法修饰符上的 ACC_SYNCHRONIZED 实现

Lock

====

public interface Lock {

void lock();

void lockInterruptibly() throws InterruptedException;

boolean tryLock();

boolean tryLock(long time, TimeUnit unit) throws InterruptedException;

void unlock();

Condition newCondition();

}

| 方法 | 说明 |

| — | — |

| lock() | 用来获取锁,如果锁被其他线程获取,处于等待状态。如果采用 Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在 try{}catch{} 块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。 |

| lockInterruptibly() | 通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。 |

| tryLock() | tryLock 方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回 true,如果获取失败(即锁已被其他线程获取),则返回 false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。 |

| tryLock(long,TimeUnit) | 与 tryLock 类似,只不过是有等待时间,在等待时间内获取到锁返回 true,超时返回 false。 |

锁的分类


悲观锁、乐观锁

悲观锁认为自己在使用数据的时候一定有别的线程来修改数据,因此在获取数据的时候会先加锁,确保数据不会被别的线程修改。Java 中,synchronized 关键字和 Lock 的实现类都是悲观锁。悲观锁适合写操作多的场景,先加锁可以保证写操作时数据正确。

而乐观锁认为自己在使用数据时不会有别的线程修改数据,所以不会添加锁,只是在更新数据的时候去判断之前有没有别的线程更新了这个数据。如果这个数据没有被更新,当前线程将自己修改的数据成功写入。如果数据已经被其他线程更新,则根据不同的实现方式执行不同的操作(例如报错或者自动重试)。乐观锁在 Java 中是通过使用无锁编程来实现,最常采用的是 CAS 算法,Java 原子类中的递增操作就通过 CAS 自旋实现。乐观锁适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。

自旋锁、适应性自旋锁

阻塞或唤醒一个 Java 线程需要操作系统切换 CPU 状态来完成,这种状态转换需要耗费处理器时间。如果同步代码块中的内容过于简单,状态转换消耗的时间有可能比用户代码执行的时间还要长。

在许多场景中,同步资源的锁定时间很短,为了这一小段时间去切换线程,线程挂起和恢复现场的花费可能会让系统得不偿失。如果物理机器有多个处理器,能够让两个或以上的线程同时并行执行,我们就可以让后面那个请求锁的线程不放弃CPU的执行时间,看看持有锁的线程是否很快就会释放锁。

而为了让当前线程“稍等一下”,我们需让当前线程进行自旋,如果在自旋完成后前面锁定同步资源的线程已经释放了锁,那么当前线程就可以不必阻塞而是直接获取同步资源,从而避免切换线程的开销。这就是自旋锁。

自旋锁本身是有缺点的,它不能代替阻塞。自旋等待虽然避免了线程切换的开销,但它要占用处理器时间。如果锁被占用的时间很短,自旋等待的效果就会非常好。反之,如果锁被占用的时间很长,那么自旋的线程只会白浪费处理器资源。所以,自旋等待的时间必须要有一定的限度,如果自旋超过了限定次数(默认是 10 次,可以使用 -XX:PreBlockSpin 来更改)没有成功获得锁,就应当挂起线程。

自旋锁的实现原理同样也是 CAS,AtomicInteger 中调用 unsafe 进行自增操作的源码中的 do-while 循环就是一个自旋操作,如果修改数值失败则通过循环来执行自旋,直至修改成功。

死锁

当前线程拥有其他线程需要的资源,当前线程等待其他线程已拥有的资源,都不放弃自己拥有的资源。

引用类型

====

强引用 > 软引用 > 弱引用

| 引用类型 | 说明 |

| — | — |

| StrongReferenc(强引用) | 当一个对象具有强引用,那么垃圾回收器是绝对不会的回收和销毁它的,非静态内部类会在其整个生命周期中持有对它外部类的强引用 |

| WeakReference (弱引用) | 在垃圾回收器运行的时候,如果对一个对象的所有引用都是弱引用的话,该对象会被回收 |

| SoftReference(软引用) | 如果一个对象只具有软引用,若内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,才会回收这些对象的内存 |

| PhantomReference(虚引用) | 一个只被虚引用持有的对象可能会在任何时候被 GC 回收。虚引用对对象的生存周期完全没有影响,也无法通过虚引用来获取对象实例,仅仅能在对象被回收时,得到一个系统通知(只能通过是否被加入到 ReferenceQueue 来判断是否被GC,这也是唯一判断对象是否被 GC 的途径)。 |

动态代理

====

示例:

// 定义相关接口

public interface BaseInterface {

void doSomething();

}

// 接口的相关实现类

public class BaseImpl implements BaseInterface {

@Override

public void doSomething() {

System.out.println(“doSomething”);

}

}

总结

一般像这样的大企业都有好几轮面试,所以自己一定要花点时间去收集整理一下公司的背景,公司的企业文化,俗话说「知己知彼百战不殆」,不要盲目的去面试,还有很多人关心怎么去跟HR谈薪资。

这边给大家一个建议,如果你的理想薪资是30K,你完全可以跟HR谈33~35K,而不是一下子就把自己的底牌暴露了出来,不过肯定不能说的这么直接,比如原来你的公司是25K,你可以跟HR讲原来的薪资是多少,你们这边能给到我的是多少?你说我这边希望可以有一个20%涨薪。

最后再说几句关于招聘平台的,总之,简历投递给公司之前,请确认下这家公司到底咋样,先去百度了解下,别被坑了,每个平台都有一些居心不良的广告党等着你上钩,千万别上当!!!

Java架构学习资料,学习技术内容包含有:Spring,Dubbo,MyBatis, RPC, 源码分析,高并发、高性能、分布式,性能优化,微服务 高级架构开发等等。

还有Java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书。
在这里插入图片描述

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
(弱引用) | 在垃圾回收器运行的时候,如果对一个对象的所有引用都是弱引用的话,该对象会被回收 |

| SoftReference(软引用) | 如果一个对象只具有软引用,若内存空间足够,垃圾回收器就不会回收它;如果内存空间不足了,才会回收这些对象的内存 |

| PhantomReference(虚引用) | 一个只被虚引用持有的对象可能会在任何时候被 GC 回收。虚引用对对象的生存周期完全没有影响,也无法通过虚引用来获取对象实例,仅仅能在对象被回收时,得到一个系统通知(只能通过是否被加入到 ReferenceQueue 来判断是否被GC,这也是唯一判断对象是否被 GC 的途径)。 |

动态代理

====

示例:

// 定义相关接口

public interface BaseInterface {

void doSomething();

}

// 接口的相关实现类

public class BaseImpl implements BaseInterface {

@Override

public void doSomething() {

System.out.println(“doSomething”);

}

}

总结

一般像这样的大企业都有好几轮面试,所以自己一定要花点时间去收集整理一下公司的背景,公司的企业文化,俗话说「知己知彼百战不殆」,不要盲目的去面试,还有很多人关心怎么去跟HR谈薪资。

这边给大家一个建议,如果你的理想薪资是30K,你完全可以跟HR谈33~35K,而不是一下子就把自己的底牌暴露了出来,不过肯定不能说的这么直接,比如原来你的公司是25K,你可以跟HR讲原来的薪资是多少,你们这边能给到我的是多少?你说我这边希望可以有一个20%涨薪。

最后再说几句关于招聘平台的,总之,简历投递给公司之前,请确认下这家公司到底咋样,先去百度了解下,别被坑了,每个平台都有一些居心不良的广告党等着你上钩,千万别上当!!!

Java架构学习资料,学习技术内容包含有:Spring,Dubbo,MyBatis, RPC, 源码分析,高并发、高性能、分布式,性能优化,微服务 高级架构开发等等。

还有Java核心知识点+全套架构师学习资料和视频+一线大厂面试宝典+面试简历模板可以领取+阿里美团网易腾讯小米爱奇艺快手哔哩哔哩面试题+Spring源码合集+Java架构实战电子书。
[外链图片转存中…(img-5IK90ZYV-1713445351099)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-xEkURlLO-1713445351100)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 19
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值