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”);
}
}
public static void main(String args[]) {
BaseImpl base = new BaseImpl();
// Proxy 动态代理实现
BaseInterface proxyInstance = (BaseInterface) Proxy.newProxyInstance(base.getClass().getClassLoader(), base.getClass().getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (method.getName().equals(“doSomething”)) {
method.invoke(base, args);
System.out.println(“do more”);
}
return null;
}
});
proxyInstance.doSomething();
}
Proxy.java
public class Proxy implements java.io.Serializable {
// 代理类的缓存
private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());
···
// 生成代理对象方法入口
public static Object newProxyInstance(ClassLoader loader,
Class<?>[] interfaces,
InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
// 找到并生成相关的代理类
Class<?> cl = getProxyClass0(loader, intfs);
// 调用代理类的构造方法生成代理类实例
try {
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
cons.setAccessible(true);
}
return cons.newInstance(new Object[]{h});
}
···
}
···
// 定义和返回代理类的工厂类
private static final class ProxyClassFactory
implements BiFunction<ClassLoader, Class<?>[], Class<?>>
{
// 所有代理类的前缀
private static final String proxyClassNamePrefix = “$Proxy”;
// 用于生成唯一代理类名称的下一个数字
private static final AtomicLong nextUniqueNumber = new AtomicLong();
@Override
public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
···
String proxyPkg = null; // 用于定义代理类的包名
int accessFlags = Modifier.PUBLIC | Modifier.FINAL;
// 确保所有 non-public 的代理接口在相同的包里
for (Class<?> intf : interfaces) {
int flags = intf.getModifiers();
if (!Modifier.isPublic(flags)) {
accessFlags = Modifier.FINAL;
String name = intf.getName();
int n = name.lastIndexOf(‘.’);
String pkg = ((n == -1) ? “” : name.substring(0, n + 1));
if (proxyPkg == null) {
proxyPkg = pkg;
} else if (!pkg.equals(proxyPkg)) {
throw new IllegalArgumentException(
“non-public interfaces from different packages”);
}
}
}
if (proxyPkg == null) {
// 如果没有 non-public 的代理接口,使用默认的包名
proxyPkg = “”;
}
{
List methods = getMethods(interfaces);
Collections.sort(methods, ORDER_BY_SIGNATURE_AND_SUBTYPE);
validateReturnTypes(methods);
List<Class<?>[]> exceptions = deduplicateAndGetExceptions(methods);
Method[] methodsArray = methods.toArray(new Method[methods.size()]);
Class<?>[][] exceptionsArray = exceptions.toArray(new Class<?>[exceptions.size()][]);
// 生成代理类的名称
long num = nextUniqueNumber.getAndIncrement();
String proxyName = proxyPkg + proxyClassNamePrefix + num;
// Android 特定修改:直接调用 native 方法生成代理类
return generateProxy(proxyName, interfaces, loader, methodsArray,
exceptionsArray);
// JDK 使用的 ProxyGenerator.generateProxyClas 方法创建代理类
byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
proxyName, interfaces, accessFlags);
try {
return defineClass0(loader, proxyName,
proxyClassFile, 0, proxyClassFile.length);
} ···
}
}
···
// 最终调用 native 方法生成代理类
@FastNative
private static native Class<?> generateProxy(String name, Class<?>[] interfaces,
ClassLoader loader, Method[] methods,
Class<?>[][] exceptions);
}
ProxyGenerator.java
public static byte[] generateProxyClass(final String name,
Class[] interfaces)
{
ProxyGenerator gen = new ProxyGenerator(name, interfaces);
final byte[] classFile = gen.generateClassFile();
if (saveGeneratedFiles) {
java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() {
public Void run() {
try {
FileOutputStream file =
new FileOutputStream(dotToSlash(name) + “.class”);
file.write(classFile);
file.close();
return null;
} catch (IOException e) {
throw new InternalError(
"I/O exception saving generated file: " + e);
}
}
});
}
return classFile;
}
元注解
===
@Retention:保留的范围,可选值有三种。
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V:vip204888 备注Android获取(资料价值较高,非无偿)
结尾
- 腾讯T4级别Android架构技术脑图;查漏补缺,体系化深入学习提升
- 一线互联网Android面试题含详解(初级到高级专题)
这些题目是今年群友去腾讯、百度、小米、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。并且大多数都整理了答案,熟悉这些知识点会大大增加通过前两轮技术面试的几率
有Android开发3-5年基础,希望突破瓶颈,成为架构师的小伙伴,可以关注我
去过华为、OPPO等大厂,18年进入阿里一直到现在。**
深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-21fCf1TR-1711549202436)]
[外链图片转存中…(img-73Zo2cGH-1711549202437)]
[外链图片转存中…(img-q6ewo14h-1711549202437)]
[外链图片转存中…(img-l6rnCkW3-1711549202437)]
[外链图片转存中…(img-ynLU4ZOH-1711549202438)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
[外链图片转存中…(img-UFtSiijg-1711549202438)]
[外链图片转存中…(img-fWG0oMaZ-1711549202438)]
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V:vip204888 备注Android获取(资料价值较高,非无偿)
[外链图片转存中…(img-ahW0pfWq-1711549202439)]
结尾
- 腾讯T4级别Android架构技术脑图;查漏补缺,体系化深入学习提升
[外链图片转存中…(img-VmSrCh26-1711549202439)]
- 一线互联网Android面试题含详解(初级到高级专题)
这些题目是今年群友去腾讯、百度、小米、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。并且大多数都整理了答案,熟悉这些知识点会大大增加通过前两轮技术面试的几率
[外链图片转存中…(img-ZEgrBCvR-1711549202439)]
有Android开发3-5年基础,希望突破瓶颈,成为架构师的小伙伴,可以关注我