@autowired和@resource的区别@Autowire和@Resource都是Spring Framework中用于注入依赖的注解，它们的主要区别在于：\1. @Autowired是Spring自带的注解，@Resource是JSR-250规范中定义的注解。\2. @Autowired默认按照byType的方式进行依赖注入，如果有多个符合条件的bean，则按照bean的名称进行匹配和注入。@Resource默认按照byName方式进行依赖注入，如果找不到符合名称的bean，则按照byType进行匹配和注入。\3. @Autowired可以用在字段、方法、构造函数上，@Resource只能用在字段和方法上。\4. @Autowired可以不需要指定名称，@Resource必须指定名称或者类型。\5. @Autowired属于Spring在自己的框架中的一种注入方式，使用起来更加简单方便，而@Resource则属于JavaEE规范的一种注入方式，可以用于JavaEE平台的任何环境中。因此，一般来说，@Autowired更加常用，而@Resource则更适用于JavaEE平台下的应用程序。innoDB和myIsam区别\1. 存储方式不同：InnoDB采用了聚簇索引的方式，所以数据文件和索引文件是在一起的，而MyISAM采用了非聚簇索引的方式，数据文件和索引文件是分开的。\2. 支持事务和外键：InnoDB支持事务和外键，而MyISAM不支持。\3. 并发能力不同：InnoDB具有更好的并发能力，可以在高并发的情况下保持性能稳定，而MyISAM在高并发情况下性能会下降。\4. 锁粒度不同：InnoDB的锁粒度比MyISAM更细，可以控制行级锁，而MyISAM只能控制表级锁。\5. 数据缓存机制不同：InnoDB采用了缓存池的机制，可以自动调整内存大小并保持高效，而MyISAM使用了操作系统缓存机制，需要手动调整缓存大小。\6. 数据完整性不同：InnoDB支持事务，可以保证数据的完整性和一致性，而MyISAM不支持事务，不能保证数据的完整性和一致性。\7. 性能表现不同：在查询方面，MyISAM的速度比InnoDB快，但在并发访问和修改方面，InnoDB的效率比MyISAM高。为什么MyISAM比InnoDB的查询快\1. MyISAM是基于索引的存储引擎，而InnoDB是基于聚集索引的存储引擎。MyISAM的索引结构更加简单，查询效率更高。\2. MyISAM不支持事务，因此不需要维护事务日志和行级锁，这样就减少了系统开销，提高了查询速度。\3. MyISAM采用较为简单的表锁定机制，可以对整个表进行加锁，而不是行级锁。这样对于大量小查询的应用程序，MyISAM比InnoDB具有更好的性能。\4. MyISAM表的数据和索引存储在不同的文件中，可以针对不同的查询优化存储结构，因此查询速度更快。但是需要注意的是，MyISAM不支持事务和行级锁等高级功能，因此在高并发、大数据量的应用场景下，InnoDB表现更加稳定和安全。springboot常用注解\1. @SpringBootApplication：标注启动类或者配置类，说明这是一个Spring Boot应用。\2. @RestController：标注在Controller类上，用于替换@Controller和@ResponseBody。（要返回json的时候用）\3. @RequestMapping：处理请求地址映射，可以标注在类或者方法上，用于指定请求的URL。\4. @GetMapping：处理HTTP GET请求。\5. @PostMapping：处理HTTP POST请求。\6. @PutMapping：处理HTTP PUT请求。\7. @DeleteMapping：处理HTTP DELETE请求。\8. @PathVariable：获取路径参数，标注在参数前面。\9. @RequestBody：获取请求体中的数据，标注在参数前面。\10. @RequestHeader：获取请求头中的数据，标注在参数前面。\11. @RequestParam：获取请求参数中的数据，标注在参数前面。\12. @Valid：开启数据校验。\13. @Autowired：自动注入依赖。\14. @Qualifier：指定注入的对象名称。\15. @Bean：标注在方法上，表示将方法返回的对象注入Spring容器中，可以用来创建第三方对象。\16. @ConfigurationProperties：绑定配置文件中的属性。\17. @ComponentScan：指定组件扫描范围。\18. @Conditional：根据条件决定是否注入Bean。\19. @Transactional：开启事务。\20. @EnableCaching：开启缓存。@Configuration和@Component有什么区别@Configuration和@Component的区别在于它们被用于不同的场景。@Configuration是用于定义配置类的注解，用于提供一种替代XML配置的方式。使用@Configuration注解的类可以包含@Bean注解，用于声明需要注入的对象。这些对象可以是其他类的实例或对象工厂。@Component是用于定义组件的注解，用于将一个类标记为一个Bean，即让Spring容器自动地管理这个类的生命周期。常见的@Component注解有@Component、@Service、@Controller和@Repository等。因此，@Configuration通常用于定义@Configuration类，@Bean注解用于定义Bean，而@Component通常用于标记组件类。@SpringBootApplication由什么组成?@SpringBootApplication 注解包含了以下三个注解：@Configuration：将该类标记为应用上下文中的一个 Bean 定义的来源。@EnableAutoConfiguration：启用 Spring Boot 的自动配置机制，根据当前应用所添加的 jar 包，来推测并配置出合适的 Bean。@ComponentScan：启用组件扫描，自动扫描应用程序包及其子包下的所有类，将所有被 @Component 等注解标记的类作为 Bean 定义加载到应用上下文中。JavaWeb四大域对象\1. ServletContext：代表整个Web应用程序，通常用于获取Web应用级别的初始化参数和共享的对象等。\2. HttpSession：代表一个用户会话，通常用于存储与特定用户相关的信息，例如登录信息、购物车信息等。\3. HttpServletRequest：代表一个HTTP请求，通常用于获取客户端发送的请求参数和请求头等。\4. HttpServletResponse：代表一个HTTP响应，通常用于设置响应头和响应内容，例如指定响应编码、输出文本内容等。这四大域对象在JavaWeb开发中扮演着重要的角色，在不同层级上存储和共享信息，方便开发人员快速获取和处理数据。比较常用的是HttpServletRequest与HttpServletResponse，这两个对象可以共同完成请求与响应的处理。项目用了哪些dependency1.springframework框架2.junit 使用@Test测试项目3.c3p0 数据连接池4.gson 用于ResponBody返回json文件，用来ajax的传输5.ServletAPI 有common-file和Thymeleaf6.mybatis相关 ，包括mybatisgenerator7.kaptcha谷歌的开源验证码包ArrayList 和 Vector 的区别?ArrayList 是 List 的主要实现类，底层使用 Object[]存储，适用于频繁的查找工作，线程不安全 ；Vector 是 List 的古老实现类，底层使用 Object[] 存储，线程安全的。ArrayList 与 LinkedList 区别?是否保证线程安全： ArrayList 和 LinkedList 都是不同步的，也就是不保证线程安全；底层数据结构： ArrayList 底层使用的是 Object 数组；LinkedList 底层使用的是 双向链表 数据结构HashMap和HashTable用过吗？使用场景有哪些？有什么区别？\1. 线程安全性方面：HashMap是非线程安全的，多个线程同时访问一个HashMap实例会发生并发修改异常；而HashTable是线程安全的，使用synchronized来确保同步，但是在并发性能方面较HashMap差。\2. 继承体系方面：HashMap继承AbstractMap类，实现Map接口；而HashTable继承Dictionary抽象类，实现Map接口。\3. null键和null值的使用方面：HashMap使用null键和null值是可接受的，而HashTable则不允许null键和null值。底层put过程：当向HashMap中插入一个元素时，首先将键通过hashCode()方法计算出它的哈希值，然后通过hash()方法将这个哈希值定位到对应的存储位置，如果该位置没有元素则直接插入，如果该位置已经有元素则遍历链表（或红黑树），如果发现有相同的键则用新的值替换旧的值，否则将新的键值对插入到链表（或红黑树）的末端。而HashTable则使用synchronized关键字来保证线程安全。HashTable怎么实现线程安全？为什么加锁？又想效率高又想线程安全用什么？HashTable实现线程安全的方法通常是使用锁机制，例如使用Mutex（互斥锁）或Semaphore（信号量）等工具，来保证多个线程同时访问HashTable时不会出现错误。加锁的原因是因为HashTable是一种共享数据结构，多个线程可能同时对它进行读写操作，如果不进行加锁操作，在多线程情况下可能会出现数据竞争和不一致的情况。为了既想保证线程安全，又想保证高效，可以采用一些优化技术，例如ConcurrentHashMap或ConcurrentSkipListMap等线程安全的数据结构。这些数据结构利用分段锁和CAS（Compare and Swap）等技术，可以实现高并发高效率的数据访问和更新。CurrentHashmap怎么实现的?CurrentHashMap是通过实现ConcurrentMap接口来实现的，它的底层实现是通过分割桶的方式来实现高并发的线程访问。在ConcurrentHashMap中，数据被分割为一段一段的存储空间，每一段被称为一个bucket（桶），每个bucket里面存放的是HashMap的Node节点，每个节点就是一个key-value键值对。在put或get元素时，会根据key的hash值来确定该元素在哪个bucket里面，然后对该bucket进行操作，因此不同的线程可以同时对不同的bucket进行操作，从而实现高并发的访问。并发的扩容也是通过分段的方式实现的，当一个桶的元素个数超过阈值时，则会对该桶进行扩容，如果扩容导致不同桶之间的元素分配改变，则需要进行元素的迁移，因此在迁移元素时需要保持线程安全，这也是ConcurrentHashMap能够高效并发的关键所在。comparable 和 Comparator 的区别comparable 接口实际上是出自 java.lang包 它有一个 obj1.compareTo(Object obj2)方法用来排序comparator接口实际上是出自 java.util 包它有一个 compare(Object obj1, Object obj2)方法用来排序一般我们需要对一个集合使用自定义排序时，我们就要重写 compareTo()方法或 compare()方法，当我们需要对某一个集合实现两种排序方式，比如一个 song 对象中的歌名和歌手名分别采用一种排序方法的话，我们可以重写 compareTo()方法和使用自制的 Comparator方法或者以两个 Comparator 来实现歌名排序和歌星名排序，第二种代表我们只能使用两个参数版的 Collections.sort()比较 HashSet、LinkedHashSet 和 TreeSet 三者的异同HashSet、LinkedHashSet 和 TreeSet 都是 Set 接口的实现类，都能保证元素唯一，并且都不是线程安全的。HashSet、LinkedHashSet 和 TreeSet 的主要区别在于底层数据结构不同。HashSet 的底层数据结构是哈希表（基于 HashMap 实现）。LinkedHashSet 的底层数据结构是链表和哈希表，元素的插入和取出顺序满足 FIFO。TreeSet 底层数据结构是红黑树，元素是有序的，排序的方式有自然排序和定制排序。底层数据结构不同又导致这三者的应用场景不同。HashSet 用于不需要保证元素插入和取出顺序的场景，LinkedHashSet 用于保证元素的插入和取出顺序满足 FIFO 的场景，TreeSet 用于支持对元素自定义排序规则的场景。怎么实现多线程？继承Thread类通过继承Thread类来实现多线程，需要重写run()方法，并在该方法中定义线程执行的任务。 public class MyThread extends Thread { public void run() { // 线程执行的任务 } }启动线程的方式是创建线程对象并调用start()方法。 MyThread thread = new MyThread(); thread.start();实现Runnable接口实现Runnable接口是另一种实现多线程的方式，在该接口中只定义了一个run()方法，需要在该方法中定义线程执行的任务。 public class MyRunnable implements Runnable { public void run() { // 线程执行的任务 } }启动线程的方式是先创建线程对象，再将其作为参数传入Thread的构造方法中，并调用start()方法。 MyRunnable runnable = new MyRunnable(); Thread thread = new Thread(runnable); thread.start();相比继承Thread类，实现Runnable接口有以下优点：Java是单继承语言，继承Thread类可能会限制类的继承关系；实现Runnable接口更符合面向接口编程的设计原则；实现Callable接口Callable接口是Java中的一个函数式接口，它定义了一个返回结果且可能会抛出异常的方法call()。Future接口是一个接口，表示异步计算的结果。Callable和Future一般一起使用，可以在另一个线程中执行一个耗时的计算任务，并且在主线程中获取计算结果。下面是Callable和Future的使用示例：1.创建Callable实例 public class MyCallable implements Callable<Integer> { public Integer call() throws Exception { int sum = 0; for(int i = 1; i <= 100; i++) sum += i; Thread.sleep(5000); return sum; } }2.创建FutureTask实例 Callable<Integer> callable = new MyCallable(); FutureTask<Integer> futureTask = new FutureTask<>(callable);3.创建线程，启动FutureTask实例 Thread thread = new Thread(futureTask); thread.start();4.获取计算结果，如果任务还没有完成，会阻塞当前线程 int result = futureTask.get();线程池的使用ThreadPoolExecutor是Java中内置的线程池实现类，可以用来控制线程池的大小、任务队列、拒绝策略等。下面是ThreadPoolExecutor的使用方法：创建ThreadPoolExecutor对象，主要是设置线程池的基本属性，例如corePoolSize、maximumPoolSize、keepAliveTime、workQueue、threadFactory等。例如：ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor( 10, // corePoolSize 50, // maximumPoolSize 60, // keepAliveTime TimeUnit.SECONDS, // TimeUnit new LinkedBlockingQueue<Runnable>(), // workQueue Executors.defaultThreadFactory(), // threadFactory new ThreadPoolExecutor.AbortPolicy() // handler );将任务提交给线程池处理，可以通过execute()方法或者submit()方法提交任务，其中execute()方法提交的是Runnable任务，submit()方法提交的是Callable任务。例如：threadPoolExecutor.execute(new Runnable() { @Override public void run() { // 执行任务的代码 } }); Future<String> future = threadPoolExecutor.submit(new Callable<String>() { @Override public String call() throws Exception { // 执行任务的代码 return "result"; } });Spring如何解决循环依赖Spring 通过三级缓存解决了循环依赖：第一级缓存是 singletonObjects，用于存放完全创建成功的 bean。如果在创建 A 的过程中需要创建 B，但是 B 已经在 singletonObjects 中，就可以直接返回，避免循环依赖。第二级缓存是 earlySingletonObjects，用于存放尚未完全创建成功的 bean。如果在创建 A 的过程中需要创建 B，但是 B 此时还没有完全创建成功，就需要把 A 临时保留在 earlySingletonObjects 中，等 B 创建成功后再进行依赖注入。第三级缓存是 singletonFactories，用于存放创建中的 bean 工厂对象。如果在创建 A 的过程中需要创建 B，但是 B 此时还没有完全创建成功并且不在 earlySingletonObjects 中，就需要通过 B 的创建工厂生成 B 的代理对象，代替 B 的实例，等到 B 成功创建后再进行依赖注入。通过这三级缓存，Spring 可以正确地处理循环依赖，保证 Spring 应用在 bean 的创建过程中不出现死循环、死锁等问题。JDK和JRE的区别Java开发工具包（JDK）和Java运行时环境（JRE）是两个不同的软件包，用于开发和运行Java程序。它们之间的主要区别如下：JDK（Java Development Kit）：- JDK是用于开发Java程序的软件包，包含了编译器、调试器、文档生成器等工具。- JDK包含JRE，所以，如果要开发Java程序，必须先安装JDK。- JDK提供了完整的Java开发环境，可以编写、编译和调试Java程序。JRE（Java Runtime Environment）：- JRE是用于运行Java程序的软件包，包含了Java虚拟机（Java Virtual Machine，JVM）、类库和其他支持文件。- JRE不包含编译器、调试器等开发工具，只能运行Java程序。- JRE适用于只需要运行Java程序，而不需要进行开发的用户。综上所述，JDK是面向开发者的软件包，其中包含了JRE，而JRE则是面向普通用户的软件包，只是用于运行Java程序。项目中事务怎么处理的？手动try..catch了事务还能回滚吗？当时自己写的时候只是简单的判断了传入文件或者对象的数据完整性和合理性，没有使用事务，但是通过后面的学习我知道应该使用try-catch去处理这些修改，在catch中使用 connection.rollback();手动回滚事务。子查询弊端？为什么使用左外右外不用内连接？\1. 子查询的弊端- 性能问题：子查询是在主查询之后被执行的，需要进行多次查询，会导致性能下降；同时，子查询中如果涉及到大量的数据集，也会影响查询效率。- 可读性问题：多重嵌套的子查询语句会使查询语句变得难以理解和维护；而且，子查询中的逻辑很可能已经可以通过简单的联结实现，因此也会增加开发人员的负担。\2. 为什么使用左外右外不用内连接？左外连接和右外连接解决的是表之间的关系问题，而内连接则是解决的两个表中相同的数据问题，因此在不同的场景下，选择不同的连接方式是非常必要的：- 如果我们需要查询一个表的所有数据，即使在另一个表中没有对应的内容，那么左外连接和右外连接是更好的选择。当使用内连接时，没有对应数据的那些记录将会被过滤掉。- 如果我们需要通过两个表之间相同的字段值来进行联合查询，例如订单和订单详情表中的订单ID，这时我们需要使用内连接来获取这些共同的信息，左右连接是无法提供此类信息的。springAOP注解有哪些？Spring AOP 的注解主要有以下几种：@Aspect：将一个类定义为切面类。@Pointcut：定义一个切入点，其中表达式指定连接点的位置。@Before：定义在方法执行前执行的 advice。@AfterReturning：定义在方法执行成功后执行的 advice。@AfterThrowing：定义在方法抛出异常后执行的 advice。@After：定义在方法执行结束后执行的 advice。@Around：定义环绕 advice, 它组合了前置、后置、异常处理和最终处理通知，可以在方法执行前后增强，也可以手动选择是否执行被代理对象的方法。@DeclareParents：引入新的接口并为其创建代理。除了以上这些基本注解外，Spring 还提供了其他注解用于增强 AOP 的功能，例如@Order注解用于设置切面的优先级别，@EnableAspectJAutoProxy注解用于开启自动代理，等等。概括说说Java中的代理模式分为静态代理和动态代理：静态代理：在编译时就进行增强，也就是直接重新根据原接口写一个增强类，最后生成的对象也就是被增强过的了。动态代理：分为JDK动态代理和CGLIB有接口情况，使用 JDK 动态代理（直接创建新类实现该接口，增强是必须输入实现了接口的对象）public interface SmsService { String send(String message); } ----=- public class SmsServiceImpl implements SmsService { public String send(String message) { System.out.println("send message:" + message); return message; } } ------ public class DebugInvocationHandler implements InvocationHandler { /** * 代理类中的真实对象 */ private final Object target; public DebugInvocationHandler(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws InvocationTargetException, IllegalAccessException { //调用方法之前，我们可以添加自己的操作 System.out.println("before method " + method.getName()); Object result = method.invoke(target, args); //调用方法之后，我们同样可以添加自己的操作 System.out.println("after method " + method.getName()); return result; } } ------ public class JdkProxyFactory { public static Object getProxy(Object target) { return Proxy.newProxyInstance( target.getClass().getClassLoader(), // 目标类的类加载 target.getClass().getInterfaces(), // 代理需要实现的接口，可指定多个 new DebugInvocationHandler(target) // 代理对象对应的自定义 InvocationHandler ); } } ------ SmsService smsService = (SmsService) JdkProxyFactory.getProxy(new SmsServiceImpl()); smsService.send("java");没有接口情况，使用 CGLIB 动态代理（直接使用继承创建该类的子类）在 CGLIB 动态代理机制中 MethodInterceptor 接口和 Enhancer 类是核心。你需要自定义 MethodInterceptor 并重写 intercept 方法，intercept 用于拦截增强被代理类的方法。public class AliSmsService { // 不同于jdk 这个不用实现接口 public String send(String message) { System.out.println("send message:" + message); return message; } } ------ import net.sf.cglib.proxy.MethodInterceptor; import net.sf.cglib.proxy.MethodProxy; import java.lang.reflect.Method; /** * 自定义MethodInterceptor */ public class DebugMethodInterceptor implements MethodInterceptor { /** * @param o 被代理的对象（需要增强的对象） * @param method 被拦截的方法（需要增强的方法） * @param args 方法入参 * @param methodProxy 用于调用原始方法 */ @Override public Object intercept(Object o, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { //调用方法之前，我们可以添加自己的操作 System.out.println("before method " + method.getName()); Object object = methodProxy.invokeSuper(o, args); //调用方法之后，我们同样可以添加自己的操作 System.out.println("after method " + method.getName()); return object; } } ------ import net.sf.cglib.proxy.Enhancer; public class CglibProxyFactory { public static Object getProxy(Class<?> clazz) { // 创建动态代理增强类 Enhancer enhancer = new Enhancer(); // 设置类加载器 enhancer.setClassLoader(clazz.getClassLoader()); // 设置被代理类 enhancer.setSuperclass(clazz); // 设置方法拦截器 enhancer.setCallback(new DebugMethodInterceptor()); // 创建代理类 return enhancer.create(); } } ------ AliSmsService aliSmsService = (AliSmsService) CglibProxyFactory.getProxy(AliSmsService.class); aliSmsService.send("java"); JDK和JRE有什么区别JDK（Java Development Kit）是用于编写和开发Java程序的工具包，它包含了JRE（Java Runtime Environment）和一系列工具和库，能用于编写、调试和运行Java应用程序和Applet。JDK适用于需要进行Java程序开发的用户。JRE是Java程序的运行时环境，它包含了Java虚拟机（JVM）和基本的类库。使用JRE可以运行已经编写好的Java应用程序和Applet，但不能进行Java程序的开发和编译工作。JRE适用于只需要运行Java程序的用户。所以JDK和JRE的主要区别在于: \1. JDK包含JRE，但JRE不包含JDK； \2. JDK可以用于Java程序的开发，而JRE只能用于Java程序的运行； \3. JDK包含了一些开发工具，编译器，调试器和一系列的类库；而JRE只包含了Java虚拟机和基本的类库。总之，如果您只需要运行Java应用程序和Applet, 那么只需要安装JRE即可；而如果你需要进行Java程序的开发，那么需要安装JDK。sql有哪些索引方式？按数据结构分类：B+tree索引、Hash索引、Full-text索引。按物理存储分类：聚集索引、非聚集索引（也叫二级索引、辅助索引）。按字段特性分类：主键索引(PRIMARY KEY)、唯一索引(UNIQUE)、普通索引(INDEX)、全文索引(FULLTEXT)。按字段个数分类：单列索引、联合索引（也叫复合索引、组合索引）。一般回答按字段特性分类：主键索引(PRIMARY KEY)、唯一索引(UNIQUE)、普通索引(INDEX)、全文索引(FULLTEXT)。主键索引(PRIMARY KEY)建立在主键上的索引被称为主键索引，一张数据表只能有一个主键索引，索引列值不允许有空值，通常在创建表时一起创建。唯一索引(UNIQUE)建立在UNIQUE字段上的索引被称为唯一索引，一张表可以有多个唯一索引，索引列值允许为空，列值中出现多个空值不会发生重复冲突。普通索引(INDEX)建立在普通字段上的索引被称为普通索引。全文索引(FULLTEXT)MyISAM 存储引擎支持Full-text索引，用于查找文本中的关键词，而不是直接比较是否相等。Full-text索引一般使用倒排索引实现，它记录着关键词到其所在文档的映射。InnoDB 存储引擎在 MySQL 5.6.4 版本中也开始支持Full-text索引。Mybatis # 和 $在Mybatis中，#和 $都是用于占位符的，其中 #会将参数转化为字符串，而 $则是直接替换成参数值。#可以有效防止SQL注入，$则需要注意防范SQL注入。redis端口号6379redis默认有几个库Synchronized对象锁和类锁的区别？java对象锁对象锁是用于对象实例方法，或者一个对象实例上的。若实例对象被lock，则该实例对象的所有同步方法全被lockjava类锁类锁是用于类的静态方法或者一个类的class对象上的。若类对象被lock，则类对象的所有同步方法全被lock 总体来说，当Synchronized加到static方法前面就是给class加锁，即类锁；而Synchronized加到非静态方法前面是给对象上锁 对象锁和类锁是不同的锁，所以多线程同时执行这两个不同锁的方法时，是异步的。 类锁是同步执行的。对象锁锁的对象不一样，所以是异步执行的。 如果多线程同时访问同一类的类锁以及对象锁，这两个方法执行是异步的，原因：类锁和对象锁是两种不同的锁 类锁对该类的所有对象都能起到作用，而对象锁不能。synchronized中sleep和wait的区别1) sleep是Thread类的方法，wait是Object类中定义的方法；***2) sleep方法可以在任何地方使用，wait方法只能在被冠以synchronized的方法或synchronized块中使用；***3) Thread.sleep只会让出CPU，不会导致锁行为的改变，Object.wait不仅让出CPU，还会释放已经占有的同步资源锁。notify和notifyAll的区别notify只会随机选取一个处于等待池中的线程进入锁池去竞争锁；notifyAll会让所有处于等待池中的线程全部进入锁池去竞争锁。多线程循环打印1-10synchronized方式来实现public class Main { private static volatile int number = 0; private static final Object object = new Object(); // 对象锁 public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { do { synchronized (object) { while (number % 2 == 0) { try { object.wait(); Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("odd : " + number++); object.notifyAll(); } } while (number < 10); } }).start(); new Thread(new Runnable() { @Override public void run() { do { synchronized (object) { while (number % 2 != 0) { try { object.wait(); Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("even : " + number++); object.notifyAll(); } } while (number < 10); } }).start(); } }使用 ReentrantLockpublic class Main { private static volatile int number = 0; private static ReentrantLock lock = new ReentrantLock(); private static Condition oddC = lock.newCondition(); private static Condition evenC = lock.newCondition(); public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { do { lock.lock(); try{ // 同步代码块 while(number % 2 == 0){ // 这里是用while，不使用if，还是为了防止虚假唤醒 oddC.await(); } System.out.println("odd : " + number++); evenC.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } while (number < 10); } }).start(); new Thread(new Runnable() { @Override public void run() { do { lock.lock(); try{ while(number % 2 != 0){ evenC.await(); } System.out.println("even : " + number++); oddC.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } while (number < 10); } }).start(); } }Java中 String str = new String("asd")创建了几个字符串？如果“asd”之前没有创建过，则是创建了两个。若是之前创建过“asd”字符，则会直接从常量池中拿出，那么new String（）就只创建了一个。java类的加载顺序是什么？Java 类的加载顺序可以分为以下几个步骤：加载（Loading）：查找并加载类的字节码文件。类的字节码可以来自本地文件系统、网络、ZIP 文件等。在加载阶段，JVM 会根据类的全限定名查找类的字节码，并读取到内存中。验证（Verification）：验证类的字节码文件是否符合 JVM 规范，例如是否包含不合法的格式、符号引用等。准备（Preparation）：为类的静态变量分配内存，并设置默认值。静态变量的值在这个阶段被赋为默认值，例如 int 类型的默认值为 0，引用类型的默认值为 null。解析（Resolution）：将类的符号引用转换为直接引用。在 Java 中，符号引用是一种用来描述类、字段、方法等的符号，直接引用是指直接指向内存地址的指针。解析阶段将符号引用解析为直接引用，以便 JVM 在执行时能够快速访问类、字段、方法等。初始化（Initialization）：为类的静态变量赋初始值，并执行静态代码块。在这个阶段，JVM 会执行类的静态代码块，并为静态变量赋初始值。如果有父类，则先执行父类的初始化。使用（Usage）：使用类。在这个阶段，JVM 会执行类的实例代码块和构造方法，并为实例变量赋初始值。如果有父类，则先执行父类的实例代码块和构造方法。需要注意的是，类的加载顺序可能会受到类加载器的影响，不同的类加载器可能会有不同的加载顺序。另外，在类的加载过程中，JVM 会缓存已加载的类，以便下次使用时可以直接从缓存中获取，从而提高程序的执行效率。java中next() 和 nextLine() 的区别在 Java 中，next() 和 nextLine() 是 Scanner 类中用于读取输入流的方法，它们之间的区别在于它们读取的数据的类型和范围不同。next() 方法用于读取下一个标记（token），其中标记是使用分隔符（默认是空格）分割的文本，通常用于读取单个单词或数字。该方法不会读取输入流中的换行符，因此如果输入流中只有一个换行符，则 next() 方法将返回空字符串。nextLine() 方法用于读取输入流中的一行文本，包括行尾符（例如回车符或换行符）。该方法会读取输入流中的所有字符，直到遇到行尾符为止。因此，即使输入流中只有一个换行符，nextLine() 方法也会返回一个空字符串。需要注意的是，如果在 next() 方法中读取了一个标记，但是该标记后面跟着一个换行符，则该换行符会留在输入流中。如果接下来使用 nextLine() 方法读取输入流中的下一行文本，则该方法会读取输入流中的空行，并返回一个空字符串。