CountDownLatch
和CyclicBarrier
java 类加载机制?如何实现自定义类加载器?findClass 与 loadClass 的区别?
在Java中,自定义类加载器通常是通过继承**java.lang.ClassLoader
** 类并重写其findClass 方法来实现的,该方法首先调用从文件系统获取类的字节码,然后使用**defineClass
** 方法将这些字节码转换成**Class
** 对象实例。需要注意的是,loadClass
方法已经被**ClassLoader
** 实现过了,它会首先尝试调用父类加载器来加载类,只有在父类加载器加载失败的情况下,才会调用**findClass
**方法。
自定义类加载器:
java
public class MyClassLoader extends ClassLoader{
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
String path="E:\\demo\\"+name+".class";
try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
Files.copy(Paths.get(path),byteArrayOutputStream);
//得到class文件的字节数组
byte[] bytes = byteArrayOutputStream.toByteArray();
// 调用defineClass将字节码转化为Class实例
return defineClass(name,bytes,0,bytes.length);
}catch (IOException e){
e.printStackTrace();
throw new ClassNotFoundException("类文件未找到",e);
}
}
}
注意:在Java中,一个类的身份不仅由其完整名字(包括包名)决定,还由加载它的类加载器决定。换句话说,即使两个类来自同一份字节码文件,如果它们被不同的类加载器实例加载,那么在JVM中,它们也会被视为不同的类。
java
MyClassLoader classLoader=new MyClassLoader();
Class<?> aClass = classLoader.loadClass("Demo");
Class<?> aClass1 = classLoader.loadClass("Demo");
System.out.println(aClass==aClass1); //true
MyClassLoader myClassLoader=new MyClassLoader();
Class<?> aClass2 = myClassLoader.loadClass("Demo");
System.out.println(aClass==aClass2); //false
loadClass方法
loadClass
是类加载的入口点。当你的代码尝试加载一个类时(通过**Class.forName
** 、反射等方式),最终都会调用到类加载器的**loadClass
** 方法。**loadClass
**方法的主要职责是按照双亲委派模型来加载类:
- 检查类是否已加载 :首先检查这个类是否已经被加载过了。如果已加载,就直接返回该类的
Class
对象。这保证了每个类在JVM内部只有一个**Class
**实例。 - 双亲委派 :如果类还没有被加载,**
loadClass
**会先委托给父类加载器尝试加载这个类。只有当父类加载器无法加载该类时(因为它不在父类加载器的搜索范围内),才会尝试自己加载。 - 调用
findClass
方法 :如果所有父类加载器都无法加载这个类,loadClass
方法最终会调用类加载器自己的**findClass
**方法来加载这个类。
findClass方法
findClass
方法是**ClassLoader
** 的一个受保护方法,它在类加载器的类加载机制中起到实际加载类的作用。当一个类加载器的父类加载器都无法加载某个类时,这个类加载器的**findClass
**方法就会被调用。
栈会不会溢出?栈溢出一般抛什么异常?jvm 在哪里设置栈的大小?设置的参数是什
么?
如果线程请求分配的栈容量超过java虚拟机栈允许的最大容量的时候,java虚拟
机将抛出一个StackOverFlowError异常,可以通过命令行参数设置栈的大小,java -Xss512k Application,-Xms设置堆的初始大小,-Xmx设置堆的最大大小。
java 线程池?线程池构造函数的几个参数含义?keepAliveTime 解释一下?
java
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
3, // 核心线程数量
6, //最大线程数
60, //空闲临时线程最大存活时间(数值)
TimeUnit.SECONDS,//空闲临时线程最大存活时间(单位)
new ArrayBlockingQueue<>(3),//任务队列,也就是一个堵塞队列,也可以使用LinkedBlockingQueue这个阻塞队列
Executors.defaultThreadFactory(),//用线程池工具类Executors创建线程的工厂
new ThreadPoolExecutor.AbortPolicy()//任务的拒绝策略中其中一个,丢弃任务并抛出RejectedExecutionException
);
线程等待和唤醒的实现方式
- Object 类下的 wait()、notify() 和 notifyAll() 方法;
- Condition 类下的 await()、signal() 和 signalAll() 方法;
- LockSupport 类下的 park() 和 unpark() 方法。
LockSupport 类的方法说明:
- LockSupport.park():休眠当前线程。
- LockSupport.unpark(线程对象):唤醒某一个指定的线程。
线程池拒绝策略
判断线程池中的任务已经全部执行完,等所有任务都执行完之后,进行数据的组装和返回
-
使用 CountDownLatch 或 CyclicBarrier 等待所有线程都执行完之后,再执行后续流程。
-
CompletableFuture
java
ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 2, TimeUnit.SECONDS, new ArrayBlockingQueue<>(3));
CompletableFuture<Void> a = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName());
}, pool);
CompletableFuture<Void> b = CompletableFuture.runAsync(() -> {
System.out.println(Thread.currentThread().getName());
}, pool);
//等待两个线程执行完毕
CompletableFuture.allOf(a,b).get();
ConcurrentHashMap为什么不允许插入Null
volatile有序性
死锁
解决方案:
- 按照顺序加锁:尝试让所有线程按照同一顺序获取锁,从而避免死锁。
- 设置获取锁的超时时间:尝试获取锁的线程在规定时间内没有获取到锁,就放弃获取锁,避免因为长时间等待锁而引起的死锁。
死锁排查工具:jconsole 和 JVisualVM:这些是 Java 自带的监视工具,可以用于监视线程、内存、CPU 使用率等信息,从而帮助排查死锁问题。