这里介绍和java
关联比较大的知识
类加载机制
java的类加载机制
在 Java
程序启动的时候,并不会一次性加载程序中所有的 .class 文件,而是在程序的运行过程中,动态地加载相应的类到内存中。
通常情况下,Java
程序中的 .class 文件会在以下 2 种情况下被 ClassLoader
主动加载到内存中:
- 调用类构造器
- 调用类中的静态(static)变量或者静态方法
JVM 中自带 3 个类加载器:
- 启动类加载器
BootstrapClassLoader
- 扩展类加载器
ExtClassLoader
(JDK 1.9 之后,改名为 PlatformClassLoader) - 系统加载器
APPClassLoader
双亲委派模式
所谓双亲委派模式就是,当类加载器收到加载类或资源的请求时,通常都是先委托给父类加载器加载,父类为null,则使用BootstrapClassLoader
.也就是说,只有当父类加载器找不到指定类或资源时,自身才会执行实际的类加载过程。
为什么要有双亲委派策略
因为java虚拟机,使用不同的类加载器,加载同一个.class会认为是不同的类,为了保障java运行时,系统类正常加载,采用双亲委派策略
Android
中的ClassLoader
在 Android 虚拟机里是无法直接运行 .class 文件的,Android 会将所有的 .class 文件转换成一个 .dex 文件,并且 Android 将加载 .dex 文件的实现封装在 BaseDexClassLoader 中,而我们一般只使用它的两个子类:PathClassLoader
和 DexClassLoader
java内存模型
它所描述的是多线程并发、CPU 缓存等方面的内容,这里需要注意不要将 JVM 内存结构
混淆。
高速缓存产生的问题。每个处理器都有自己的高速缓存,同时又共同操作同一块主内存,当多个处理器同时操作主内存时,可能导致数据不一致,产生缓存一致性
问题。
指令重排产生的问题。
内存模型是一套共享内存系统中多线程读写操作行为的规范,这套规范屏蔽了底层各种硬件和操作系统的内存访问差异,解决了 CPU 多级缓存
、CPU 优化
、指令重排
等导致的内存访问问题,从而保证 Java 程序(尤其是多线程程序)在各种平台下对内存的访问效果一致。
在这套规范中,有一个非常重要的规则——happens-before
- 程序次序规则
- 锁定规则
- 变量规则
- 线程启动规则
- 线程中断规则
- 线程终结规则
使用 volatile
修饰 变量,使用synchronized
关键字修饰操作都可以使 操作符合 happens-before 原则。
另外,volatile
可以保证线程对变量访问的可见性
(多线程异步满足可见性
有序性
原子性
)
参考资料
Java 内存模型与线程
java锁
synchronized
和 ReentrantLock
。
synchronized 使用更简单,加锁和释放锁都是由虚拟机自动完成。 ReentrantLock
需要开发者手动去完成。但是 ReentrantLock
的使用场景更多,公平锁
读写锁
都可以在复杂场景中发挥重要作用。
ReentrantLock基本用法。
可以通过构造函数创建公平锁
。
public class ReentrantLockTest {
ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
ReentrantLockTest ll = new ReentrantLockTest();
Thread t1 = new Thread(new Runnable(){
@Override
public void run(){
ll.printLog();
}
})
Thread t2 = new Thread(new Runnable(){
@Override
public void run(){
ll.printLog();
}
});
t1.start();
t2.start();
}
public void printLog(){
try{
//加锁
lock.lock();
for(int i=0; i<5; i++){
System.out.println(Thread.currentThread().getName() + "is printing "+i);
}
} catch(Exception e) {
} finally {
//手动释放锁,注意异常情况也要保障锁释放
lock.unlock();
}
}
}
读写锁的基本用法
public class LockTest {
//创建读写锁
ReadWriteLock rwLock = new ReentrantReadWriteLock();
//获取读锁和写锁
ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
public void read() {
readLock.lock()
try {
//从缓存中读取数据
} finally {
//要手动释放锁
readLock.unlock();
}
}
public void write(){
writeLock.lock()
try {
//向缓存中写入数据
} finally {
//要手动释放锁
writeLock.unlock();
}
}
}