1. Java中ThreadLocal
骚操作。在类里面new一个ThreadLocal,传参的时候先set值,在需要用的地方get,用完remove。哈哈哈哈千万要记得remove,因为tomcat线程是复用的,不remove下次还会读取到之前的值。(一般不要用)
Java的ThreadLocal类是一个用于创建线程局部变量的工具类。它提供了一种在多线程环境下,为每个线程创建独立变量副本的机制。每个线程都可以独立地访问自己的变量副本,互不干扰。
2. java在构造器中发生this逃逸
在Java构造器中发生"this"逃逸,指的是在对象还没有完全构造完成之前,对象的引用就被其他代码获取到并使用。这种情况可能导致一些意想不到的问题。
想看效果的话,可以在构造器中定义一个循环,然后将this泄露出去,效果会很直观。
3. 为何c++和c中char占一个字节,而java中char占两个字节
C和C++中的char类型占用一个字节的原因是因为它们的char类型采用的是ASCII编码,ASCII码中一个字符占用一个字节。
Java中的char类型采用的是Unicode编码,Unicode编码中一个字符通常占用两个字节,因此Java中的char类型占用两个字节。Unicode编码支持更多的字符集,包括中文、日文、韩文等。
4. 引用类型,同样都是存储地址,为何java引用比c/c++指针更安全
- 空指针异常:在Java中,引用类型默认初始化为null,如果程序员没有显式地将其赋值为一个对象,那么它就是一个空指针。在C/C++中,指针默认不会被初始化,如果程序员没有显式地将其初始化,那么它就是一个野指针,可能会导致程序崩溃或者产生未知的行为。
- 内存泄漏:在Java中,垃圾回收器会自动回收不再使用的对象,程序员不需要手动释放内存。而在C/C++中,程序员需要手动分配和释放内存,如果程序员忘记释放内存,就会出现内存泄漏的问题。
- 指针操作的安全性:在C/C++中,程序员可以对指针进行任意的操作,包括指针的加减、解引用等操作,这可能会导致指针越界或者访问非法的内存区域。而在Java中,程序员不能对引用进行任意的操作,只能通过引用访问对象的成员变量和方法,这样可以保证程序的安全性。
5. java既然一切皆对象,为什么还要保留int等基本数据类型
为了提高程序的效率和降低内存的开销,Java也保留了基本数据类型,如int、double、boolean等。
基本数据类型在Java中是直接存储在栈中的,而对象类型则需要在堆中分配内存空间。由于栈的访问速度比堆快,因此使用基本数据类型可以提高程序的效率。
另外,基本数据类型还可以减少内存的开销。在Java中,每个对象都有一个对象头,用于存储对象的元信息,如对象的类型、哈希码等。而基本数据类型不需要对象头,因此使用基本数据类型可以减少内存的开销。
6. 为什么通过反射创建对象要比new 创建对象慢
- 运行时动态地获取类的信息,包括构造函数、方法、字段等,这些操作消耗资源。
- 反射创建对象需要进行类型检查和访问权限检查,这也会带来额外的开销。
相比之下,使用new关键字创建对象时,编译器可以在编译时就确定类的信息,因此不需要进行动态获取类信息的操作,也不需要进行类型检查和访问权限检查,因此速度更快。
7. java对象的内存结构,以及如何统计对象大小?
java对象的内存结构通常包括三个部分:对象头、实例数据和对齐填充。
- 对象头:对象头通常包含两部分,分别是Mark Word和Class Pointer。Mark Word用于存储对象的锁信息、GC信息等。Class Pointer用于指向该对象的类类型
- 实例数据:对象的属性和方法
- 对齐填充:java虚拟机要求对象的起始位置必须是8的整数倍,提高内存读取效率。因此在实例数据之后可能需要填充一些字节
在程序中,可以通过调用ObjectSizeFetcher.getObjectSize方法来获取对象的大小,例如:
String str = "Hello, world!"; long size = ObjectSizeFetcher.getObjectSize(str); System.out.println("The size of the string is " + size + " bytes.");
8. 那问题来了,为什么java虚拟机要求对象的起始位置必须是8的整数倍?
Java对象需要对齐填充是为了满足处理器的要求,提高内存访问的效率。对齐填充是指在对象的内存布局中,根据数据类型的大小和处理器的要求,对对象的字段进行调整,使得字段的起始地址能够对齐到特定的边界。
处理器在访问内存时,通常要求数据的起始地址必须对齐到特定的边界,例如4字节对齐或8字节对齐。如果对象的字段没有进行对齐填充,可能会导致处理器访问内存时需要额外的操作,从而降低内存访问效率。 通过对齐填充,可以使得对象的字段按照对齐要求排列,减少处理器访问内存时的额外开销。这样可以提高程序的性能和效率。
需要注意的是,对齐填充可能会导致对象的内存占用增加,因为填充字段本身并不存储任何有用的数据。但是这种额外的内存开销通常是可以接受的,因为它可以带来更高的内存访问效率。
(知道有这回事得了,看两天了也没弄明白,把这个疑问留给以后的自己)
9. StringUtils.containsAny
是Apache Commons Lang库中的一个方法,用于判断一个字符串中是否包含指定的字符序列中的任意一个字符。还有一个重载方法可以判断字符串。
StringUtils.containsAny("Hello, world!", "or", "rx");
10. nginx怎么拦截指定链接?
可以使用Nginx的location指令来拦截指定接口的请求,并返回指定的响应。Nginx的location指令可以存在多个,也可以嵌套使用,以实现更精细的请求匹配和处理。
还可以返回json格式的响应,例如:
location /api { add_header Content-Type application/json; return 200 '{"message": "Hello, world!"}'; }
11. 除了操作系统,大部分程序都是在用户态?
是的,除了操作系统内核,大部分程序都是在用户态运行的。用户态是指程序在操作系统中运行时只能访问自己的地址空间和CPU的部分指令集,不能直接访问操作系统内核的资源。因此,大部分应用程序、服务程序和库程序都是在用户态运行的,例如浏览器、文本编辑器、数据库服务、网络服务器、图形界面程序等等。这些程序需要通过系统调用来请求操作系统内核提供的服务或访问内核资源。只有操作系统内核本身以及一些特权级别较高的程序(例如驱动程序)才能在内核态运行,直接访问操作系统内核的资源。
12. 那java线程模型是基于哪种线程模型实现的
Java线程模型是基于内核线程模型实现的。Java线程模型中的每个Java线程都映射到一个内核线程上。Java线程调度器负责在Java线程和内核线程之间进行映射和调度。当一个Java线程被创建时,JVM会为它分配一个内核线程,并在需要时将其切换到内核态执行。Java线程模型还提供了一些高级的线程同步和通信机制,如synchronized关键字、wait()和notify()方法等,这些机制都是基于内核线程模型实现的。因此,Java线程模型可以很好地利用现代多核CPU的并行处理能力,提高程序的并发性能。
13. 什么是内核线程模型
内核线程模型是一种操作系统级别的线程模型,也被称为1:1线程模型。在内核线程模型中,每个线程都由操作系统内核来管理和调度,每个线程都映射到一个内核线程上。当一个线程被创建时,操作系统会为它分配一个内核线程,并为其分配一些资源,如CPU时间片、内存空间等。线程的调度和同步都是由操作系统内核来完成的,这需要进行频繁的用户态和内核态之间的切换,从而导致一定的性能开销。
内核线程模型的优点是线程的管理和调度都由操作系统内核来完成,具有较高的可靠性和稳定性。同时,由于每个线程都映射到一个内核线程上,可以充分利用多核CPU的并行处理能力,提高程序的并发性能。但是,由于频繁的用户态和内核态之间的切换会导致一定的性能开销,因此内核线程模型不适用于需要创建大量轻量级线程的场景。
14. mysql 的on duplicate key update
ON DUPLICATE KEY UPDATE 是 MySQL 中的一个特殊语法,用于在插入数据时,如果发现数据表中已经存在相同的主键或唯一索引,那么就会更新已有记录的数据,而不是插入一条新记录。具体来说,当使用 INSERT INTO 语句插入一条数据时,如果数据表中已经存在相同的主键或唯一索引,那么就会执行 ON DUPLICATE KEY UPDATE 后面的语句,用新的数据更新已有记录的数据。
不要轻易使用(最好就tm一辈子不要在代码中使用),会把业务逻辑带到数据库层面,降低代码可读性,不利于数据库优化。
15. awaitTermination
awaitTermination() 方法是 ExecutorService 接口中的一个方法,它的作用是等待线程池中的所有任务执行完成,并在指定的时间内等待所有任务完成。该方法会阻塞调用线程,直到线程池中的所有任务都执行完成,或者等待超时,或者当前线程被中断。
awaitTermination() 方法的语法如下:
boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException;
其中,timeout 参数指定等待的最长时间,unit 参数指定时间单位。该方法会等待指定的时间,直到线程池中的所有任务都执行完成,或者等待超时,或者当前线程被中断。如果在指定的时间内所有任务都执行完成,则返回 true,否则返回 false。
需要注意的是,调用 awaitTermination() 方法之前,必须先调用 shutdown() 方法来关闭线程池,否则该方法会立即返回 false。此外,如果线程池中的任务执行时间比较长,可能会导致 awaitTermination() 方法等待超时,因此需要根据实际情况来设置等待时间。
16. java线程池有哪几种拒绝策略?
Java线程池提供了4种拒绝策略,分别是:
- AbortPolicy(默认):直接抛出RejectedExecutionException异常。
- CallerRunsPolicy:在任务被拒绝添加后,会在调用execute方法的线程中执行该任务。
- DiscardPolicy:直接丢弃被拒绝的任务,不会给予任何处理。
- DiscardOldestPolicy:丢弃任务队列中最老的一个任务,并尝试再次提交当前任务。
17. 关于一次用java复现php项目代码
有个需求,需要把一个php项目中的一个功能搞明白,然后用java实现。看代码很简单,按照逻辑一步一步把方法替换成Java的方法,很快就做完了。但是到最后结果一直不对,原因就是一个参数没找对,一直用的开发环境的参数去解析正式环境生成的数据,后来快下班了灵机一动又去找了一下才找到真正的参数。还是太年轻了,竟然没有想到会有开发环境和正式环境。