反射
反射是一种能力,允许程序在运行时访问、检查和修改自己的结构,特别是类型的信息。在java中,反射是一种强大的机制,它允许程序在运行时动态地加载、探查和使用新类。反射本质上也是通过newInstance()方法动态创建对象的。
用途:
1.增加程序的灵活性,可以在运行过程中对类进行操作。
2.增加了代码的复用性,比如说动态代理。
3.提供了检查类的注解、泛型信息等信息,为开发框架和工具提供了便利。
缺点:
1.反射会涉及到动态类型的解析,所以会导致性能变低。
2.使用反射后,代码的可读性变差
3.反射可以绕过一些限制访问的方法和属性,会破坏代码本身的抽象性。
Java创建对象的几种方式
5种
使用new关键字
System.out.println(“-----1.用new语句创建对象,是最常见的创建对象的方法。”);User user = new User();user.setName(“王1”);
使用 Class类的 newInstance()方法
Employee emp2 = (Employee)Class.forName(“org.programming.mitra.exercises.Employee”).newInstance();
//或者Employee emp2 = Employee.class.newInstance();
使用Constructor类的newInstance方法
Constructor constructor = Employee.class.getConstructor();Employee emp3 = constructor.newInstance();
这两种newInstance方法就是大家所说的反射。事实上Class的newInstance方法内部调用Constructor的newInstance方法。
使用clone方法
调用对象的clone()方法。:类必须实现Cloneable接口,并重写其clone()方法
System.out.println(“-----4.调用对象的clone()方法”);
User user4 = (User) user.clone();
System.out.println(“对象clone()方法:” + user4);
使用反序列化
当我们序列化和反序列化一个对象,jvm会给我们创建一个单独的对象。在反序列化时,jvm创建对象并不会调用任何构造函数。为了反序列化一个对象,我们需要让我们的类实现Serializable接口。ObjectInputStream in = new ObjectInputStream(new FileInputStream(“data.obj”));
Employee emp5 = (Employee) in.readObject();
从上面的例子可以看出来,除了使用new关键字之外的其他方法全部都是转变为invokevirtual(创建对象的直接方法),使用被new的方式转变为两个调用,new和invokespecial(构造函数调用)。
线程的同步
线程的同步是为了保证多个线程按照特定的顺序、协调访问共享资源,避免数据不一致和竞争条件等问题。
HashMap和HashTable
其实就是,String s1 = “111”,和String s2 = "111"比较引用会返回true,因为字面量赋值的变量只会在常量池保持一份,而如果是String s1 = new String(“111”),String s2 = new String(“111”),每次的new String()操作都会再堆中开辟新的内存空间,此时比较引用地址,两者是不一样的,所以会返回false
构造器–特殊的方法—创建/初始化对象;
构造器的名称必须与类名一致,并且没有返回类型;
如果子类重写父类的构造方法----可能会导致对象创建和初始化过程出现混乱;
但是子类可以通过super关键字调用父类的构造器----完成对父类的初始化操作
char可以存储一个中文汉字嘛
可以,但一般不建议这样存储 因为需要转义
char字符型 2字节—存储单个字符,为unicode编码(16位表示一个编码);
而汉字常使用utf-8编码—一个中文占3字节;
存储汉字建议使用String类型
不管在编译前java文件使用何种编码,在编译后成class后,他们都是一样的----Unicode编码表示。JVM里面的任何字符串资源都是Unicode,就是说,任何String类型的数据都是Unicode编码。
JVM加载class文件读取时候使用Unicode编码方式正确读取class文件,那么原来定义的String s=“汉字”;在内存中的表现形式是Unicode编码。
MySQL 中的 “utf8” 实际上不是真正的 UTF-8, “utf8” 只支持每个字符最多 3 个字节, 对于超过 3 个字节的字符就会出错, 而真正的 UTF-8 至少要支持 4 个字节
MySQL 中的 “utf8mb4” 才是真正的 UTF-8
for-each和for的可变性:for-each循环是只读的,不能在循环过程中修改集合或数组中的元素,但是for循环可以。
首先对于ThreadLocal而言,本质上其实也是用来解决线程安全问题的,只不过和传统给共享变量加锁的方式不同,他采用的是让这个变量在每个线程中有独有一份,线程内私有就不存在线程安全问题了,从底层源码角度来看,ThreadLocal底层真正存储数据的是ThreadLocalMap对象,是一个Map结构,Map是懒加载的过程,只有一次set的时候才会创建,并且真正持有这个Map的对象是当前线程Thread对象,所以对于Threadlocal的生命周期是随线程的终止而终止的。
我们老生常谈的ThreadLocal由于弱引用,所以会出现内存泄露,其实这是个错误的说法,恰恰相反,弱引用是为了帮助我们减少内存泄露带来的影响的,比如:我们创建一个ThreadLocal对象,然后set值,此时我们本地变量持有了ThreadLocal的强引用,所以不管怎么样THreadlOCAL都不会回收,但是如果把这个变量置为null,内存中就只有Entry持有了ThreadLocal的引用了,如果Entry是强应用,而其实此时Threadlocal已经不要了,就会发生内存泄露,并且在map的底层entry数组中还持有这个entry,后续也无法判断到底这个Threadlocal有没有被回收,所以他采用了弱引用,如果使用弱引用,name不需要threadlocal是,只有一个弱引用持有,不在gcroot应用链上,就可以被回收,但是Entry内部的value是强引用,并且entry还在entry【】的引用链上,所以其实相当于只回收了key没有回收value,也会造成内存泄露,但是与强应用不同的是,在后续我们对map进行set get的时候,底层做了优化,能够去判断entry中哪些key是空的,然后间接的清除value的强应用,来间接的解决内存泄露的问题,但是如果后续一直不调用set或者get就没办法了,而在我们实际项目中,其实一般不会遇到这个弱引用导致内存泄露的问题,因为我们一般都是创建静态的THreadlocal对象,并且后续大多数场景不会出现不用置空的情况,所以其实真正出现上述情况很少,所以为了防止内存泄露,我们还是要手动remove,关于这个问题其实我一直有个疑惑,就是关于threadlocal为什么不像我们nio中分配直接内存的时候一样,搞一个引用队列,然后再被回收的时候,间接的去回收value泄露的内存,一开始我一直想不通为什么不这样干,后来发现其实如果想要通过这种方式还需要有一个现成去配合我们工作,而对于清空这个活有不能一直在干,肯定是隔一段时间清空一次,而我们现在大多数都是使用线程池线程复用,等到他情况黄花菜都凉了
java中如何实现线程之间共享传递数据
线程局部存储(ThreadLocal):
ThreadLocal类提供了线程局部变量,每个使用该变量的线程都有独立的变量副本,因此各个线程之间的值不共享。
public class ThreadLocalExample {
private static ThreadLocal<Integer> threadLocalValue = ThreadLocal.withInitial(() -> 0);
public static void main(String[] args) {
Thread childThread = new Thread(() -> {
int value = threadLocalValue.get() + 1;
threadLocalValue.set(value);
});
childThread.start();
// 父线程可以设置和获取不同的值
int value = threadLocalValue.get() + 1;
threadLocalValue.set(value);
}
}
通过方法参数传递:
可以在创建子线程时,通过构造器或Runnable接口的run方法参数传递数据。
public class ParameterPassingExample {
public static void main(String[] args) {
Data data = new Data("shared data");
Thread childThread = new Thread(new MyRunnable(data));
childThread.start();
}
}
static class MyRunnable implements Runnable {
private Data data;
public MyRunnable(Data data) {
this.data = data;
}
@Override
public void run() {
System.out.println("Data in child thread: " + data.getContent());
}
}
static class Data {
private String content;
public Data(String content) {
this.content = content;
}
public String getContent() {
return content;
}
}
使用阻塞队列(BlockingQueue)
BlockingQueue是一个线程安全的队列,可以用来在父子线程之间传递数据。
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
public class BlockingQueueExample {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
Thread childThread = new Thread(() -> {
try {
String data = queue.take(); // 从队列中取出数据
System.out.println("Data received in child thread: " + data);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
childThread.start();
// 父线程向队列中放入数据
queue.put("Hello from parent thread");
}
}
java中如何安全的停止线程
synchronized和ReentrantLock有什么区别?
synchronized 可用来修饰普通方法、静态方法和代码块,而 ReentrantLock 只能用在代码块上。
synchronized的基础使用
使用 synchronized 修饰代码块:
public void method() {
// 加锁代码
synchronized (this) {
// ...
}
}
ReentrantLock 在使用之前需要先创建 ReentrantLock 对象,然后使用 lock 方法进行加锁,使用完之后再调用 unlock 方法释放锁,具体使用如下:
public class LockExample {
// 创建锁对象
private final ReentrantLock lock = new ReentrantLock();
public void method() {
// 加锁操作
lock.lock();
try {
// ...
} finally {
// 释放锁
lock.unlock();
}
}
}