1. 创建对象的几种方式
- 使用 new 关键字:使用 new 关键字可以在堆内存中创建一个新的对象。
- 通过反射机制:通过Java反射机制,可以在运行时动态地获取类的信息并创建对象。这种方式可以通过 Class 类的 newInstance() 方法或 Constructor 类的 newInstance() 方法来创建对象。
-
Class 类的 newInstance() 方法
ClassName objectName = (ClassName)Class.forName("ClassName").newInstance();
-
Constructor 类的 newInstance() 方法
Constructor<ClassName> constructor = ClassName.class.getConstructor(parameterTypes); ClassName objectName = constructor.newInstance(parameters);
-
- 通过 clone() 方法:Cloneable接口提供了 clone() 方法,通过该方法可以创建一个对象副本。被复制的对象必须实现 Cloneable 接口。
- 通过反序列化:将一个对象序列化成字节流后,可以通过反序列化操作将其重新转换为对象。这种方式可以使用 ObjectInputStream 类的 readObject() 方法来实现。
- 使用工厂模式:通过定义一个工厂类,使用该工厂类中的静态方法来创建对象。这样可以封装对象的创建过程,并且可以根据需要返回不同类型的对象。
- 使用单例模式:通过单例模式可以确保一个类只有一个实例对象,可以使用静态方法或者静态块来创建这个唯一的实例。
- 通过依赖注入框架:使用依赖注入框架(如Spring)可以自动创建对象并注入依赖。
2. 反射机制
反射机制是指在运行时动态地获取类的信息、调用类的方法、操作类的属性等能力。通过反射机制,可以在运行时检查类的结构并对其进行操作,而无需在编译时知道类的具体信息。
Java 反射机制主要涉及以下几个核心类:
1. Class类:java.lang.class 类代表一个类的实例,在运行时,每个类都有一个对应的 Class 对象。通过 Class 类,可以获取类的构造函数、字段、方法等信息,以及实例化对象。
2. Constructor类:java.lang.reflect.Constructor 类用于表示类的构造函数。通过 Constructor 类可以创建新的类实例。
3. Method类:java.lang.reflect.Method 类用于表示类的方法。通过 Method 类可以调用类的方法。
4. Field类:java.lang.reflect.Field 类用于表示类的字段。通过Field 类可以访问和修改类的字段。
通过这些类,可以实现动态地加载类、调用类的方法、访问和修改类的属性等操作。反射机制常用于框架设计、动态代理、注解处理等领域,但由于反射操作较为灵活,也会导致性能上的一定开销,因此在实际应用中需要谨慎使用。
反射机制为Java提供了更大的灵活性和动态性,使得程序能够在运行时动态地适应不同的情况和需求。
3. Java集合框架结构
4. ArrayList与LinkedList的区别
- 数据结构
- ArrayList基于动态数组实现,通过数组存储元素,支持随机访问(根据索引快速访问元素)。
- LinkedLsit基于双向链表实现,通过节点间的引用关系存储元素,不支持随机访问,需要顺序遍历链表来访问元素。
- 插入和删除操作
- ArrayList在中间或末尾插入/删除元素时,需要移动移动后续元素,效率较低,在末尾增加元素时效率较高。
- LinkedList在插入和删除操作上效率较高,特别是在中间插入/删除元素时,只需调整相邻节点的指针。
- 访问效率
- ArrayList的随机访问效率比较高,时间复杂度为O(1)。
- LinkedList的随机访问效率较低,需要从头到尾开始顺序查找,时间复杂度为O(n)。
- 内存占用
- ArrayList在添加或删除元素时可能会出发数组的扩容货收缩操作,可能会产生额外的空间浪费。
- LinkedList的每个元素需要额外的空间存储前后节点的引用,可能会占用更多的内存。
- 如果频繁随机访问和末尾操作,可以选择ArrayList;如果需要频繁插入和删除,可以选择LinkedList。
- ArrayList 和 LinkedList *都是线程不安全的。
5. 如何保证ArrayList的线程安全?
- 使用 Collections 工具类中的 synchronizedList 方法
List<String> synchronizedList = Collections.synchronizedList(new ArrayList<>());
- 使用锁机制
在访问ArratList时使用显式的锁机制(如ReentrantLock)来保证线程安全。List<String> list = new ArrayList<>(); ReentrantLock lock = new ReentantLock(); //在访问list之前获取锁 lock.lock(); try{ //对list进行访问 }finally{ //操作完成后释放锁 lock.unlock(); }
- 使用线程安全的替代类
使用CopyOnWriteArrayList 类:CopyOnWriteArrayList 是java.util.concurrent包下的一个线程安全的ArrayList实现。它通过在修改操作时创建一个新的数组来实现线程安全,适用于读多写少的场景。List<String> copyOnWriteArrayList = new CopyOnWriteArrayList<>();
6. HashMap和HashTable的区别
HashMap和HashTable都是用于存储键值对的集合,具体区别是:
- 同步性
- HashTable是线程安全的,所有方法都是同步的,即在多线程环境下可以直接使用而无需额外的同步操作。
- HashMap不是线程安全的,如果需要在多线程环境下使用HashMap,可以通过Collections.synchronizedMap() 方法创建一个同步的HashMap。
- null值
- HashTable不允许键或值为null,如果试图存储null键或值,会抛出NullPointerException。
- HashMap允许键和值为null。
- 继承关系
- HashTable 是Dictionary 类的子类,而Dictionary类已经被废弃。
- HashMap继承自AbstractMap类。
- 迭代器
- HashTable的迭代器是fail-fast的,即在迭代过程中如果其他线程修改了HashTable,会抛出ConcurrentModificationException。
- HashMap的迭代器是fail-fast的。
7. HashMap的底层结构
在jdk1.8及之后,HashMap底层引入了红黑树,形成数组+链表+红黑树的组合。
- 数组:用"桶数组",用于基本的数据存储。
- 链表:在解决哈希冲突时,如果某个桶位上的元素数量未达到阈值(jdk8默认8),则仍旧使用链表来存储。
- 红黑树:当某个桶位上的元素数量达到阈值,且HashMap的总容量大于64时,会将链表转换为红黑树,以减少搜索时间。红黑树是一种自平衡的二叉查找树,相比于链表,它在最坏的情况下提供了更好的查找性能O((log n)).
- 扩容和重哈希:重哈希自jdk8之后被优化,减少了重新计算哈希值的需求,提高了效率。
8. ConcurrentHashMap如何保证线程的安全
在jdk8之后,放弃了分段的锁的概念,转而使用更加细粒度的锁策略,结合CAS操作和synchronized关键字来保证线程的安全。
- CAS操作:ConcurrentHashMap在某些关键操作上使用了CAS操作,比如更新计数值等。CAS操作时一种无锁的同步机制,它通过比较内存中的某个值和预期值,如果相同则更新它。由于CAS是原子操作,因此可以保证线程的安全。
- synchronized关键字:对于需要更新二链表或红黑树结点的操作,ConcurrentHashMap使用了synchronized关键字来锁定结点或者树的根节点。因此,每次只有一个线程可以修改链表或红黑树的结构,从而保证了线程的安全。
- 细粒度的锁:通过在节点级别使用synchronized而不是在整个哈希表或者段上使用锁,ConcurrentHashMap实现了更为细粒度的锁定策略。这样,即使多个线程访问相同的桶位,只要他们操作的是不同的节点,这些操作也能够并发执行。
9. 创建线程的方式
- 继承Thread类
最直接创建线程的方式。通过继承Thread类并重写其run()方法来定义线程执行的任务,然后,通过创建该类的实例并调用其start()方法来启动线程。public class MyThread extends Thread{ public void run(){ //线程任务 } } public class Main{ public static void main(String[] args){ MyThread mythread = new MeThread(); mythread.start();//启动线程 } }
- 实现Runnable接口
将实现了Runnable接口的类的实例化作为参数传递给Thread类的构造函数。这种方式更加灵活,因为Java不支持多重继承,如果此类已经继承了一个类,就不能直接继承Thread类,但可以实现Runnable接口。public class MyRunnable implements Runnable{ public void run(){ //线程任务 } } public class Main{ public static void main(String[] args){ Thread thread = new Thread(new MyRunnable()); thread.start();//启动线程 } }
- 使用Callable和FutureTask
如果线程执行完成后要返回结果,可以使用Callable接口。Callable接口类似于Runnable,但它可以返回一个结果,并且可以抛出异常。Callable任务执行完成后,可以通过FutureTask或线程池服务获取执行结果。import java.util.concurrent.Callable; import java.util.concurrent.FutureTask; public class MyCallable implements Callable<Integer>{ public Integer call() throws Exception{ //返回执行结果 return ...; } } public class Main{ public static void main(String[] args) throws Exception{ FutureTask<Integer> futurnTask = new FutureTask<>(new MtCallable()); Thread thread = new Thread(futureTask); thread.start();//启动线程 Integer result = futureTask.get();//获取执行结果 } }
- 使用线程池(Exector框架)
在实际开发中,频繁地创建和销毁线程会导致系统性能的下降。为了避免这种情况,可以使用线程池来管理和复用线程。Java提供了Executor框架来支持线程池,其中Executors类提供了方便的工厂方法来创建不同类型的线程池。import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class Main{ public static void main(String[] args){ ExecutorService executor = Executor.newFixedThreadPool(10);//创建包含10个线程的线程池 executor.execute(new Runnable(){ public void run(){ //线程任务 } }); executor.shutdown();//关闭线程池 } }
每种创建线程的方式都有其使用场景。对于简单的任务,可以直接使用Thread类或Runnable接口。当需要线程执行完毕后返回结果时,可以使用Callable和FutureTask。对于复杂的应用,推荐使用线程池来管理线程,以提高性能和资源利用率。
10. PS:什么是Executor框架
Executor框架提供了一种将任务的提交与每个任务如何运行分离开来的机制,包括线程的使用、调度等。这个框架位于java.util.concurrent包下,旨在简化并发编程,提高多线程应用程序的性能和可管理性。
- 核心接口
Executor框架的核心是Executor接口,它仅定义了一个方法execute(Runnable command),用于执行给定的任务。然而,这个接口是一个更广泛的并发框架的基础,其中包括:- ExecutorService:是Executor的子接口,提供了更为丰富的线程池管理功能,包括任务提交、关闭线程池、任务取消等。它可以执行Callable任务以及Runnable任务,并且可以返回表示任务状态和结果的Future对象。
- ScheduleExecutorService:时ExecutorService的子接口,支持任务的延迟执行或定期执行。
- 线程池
Executor框架通过不同类型的线程池提供了线程的管理机制。线程池负责分配线程给任务执行,管理空闲线程,限制线程数量等,从而避免了为每个任务创建新线程的开销。常见的线程池实现包括:- FixedThreadPool:固定大小的线程池,可以重用池中的线程,适用于负载较重的服务器。
- CachedThreadPool:一个根据需要创建新线程的线程池,但会在先前构造的线程可用时重用它们。适用于执行很多短期异步任务的程序。
- SingleThreadExecutor:单线程的Executor,它创建单个工作线程来执行任务,适用于需要保证顺序执行各个任务的场景。
- ScheduledThreadPool:一个能延迟执行任务或定期执行任务的线程池。
- 工具类
它是一个工厂类,提供了静态方法来创建不同类型的线程池。import java.util.concurrent.ExcutorService; import java.util.concurrent.Executors; public class ExecutorExample{ public static void main(String[] args){ //创建一个固定大小的线程池 ExecutorService executor = Executors.newFixedThreadPool(4); //向线程池提交任务 for(int i =0; i < 10; ++i){ int taskId = i; executor.execute(() -> { System.out.println("Executing task " + taskId + "via" + Thread.currentThread().getName()); }) } //关闭线程池 executor.shutdown(); } }
Executor框架提供了一种灵活的线程池管理机制,通过将任务的提交与人物的执行解耦,是的并发编程变得更加简单和高效。利用这个框架可以有效的控制并发任务的执行,有虎啊资源的使用,并提高应用程序的性能。
11. 并发和并行的区别
并发与并行时多任务处理时两种不同执行模式。
- 并发
并发是指系统能够在同一时间段内处理多个任务的能力。但这并不是意味着这些人任务同时执行,而是用户在感觉上时同时进行的。并发关注于如何有效的使用单个处理器(或少数几个处理器)来同时管理多个任务。涉及到任务之间的切换,即一个任务的执行可能在另一个任务开始、结束或者阻塞时发生。 - 并行
并行是指多个处理器或多核处理器上的多个计算任务同时执行的能力。并且处理的目的是通过使用多个计算资源来加速任务的执行。这通常需要任务能够被分解成独立的子任务,这些子任务可以同时在不同的CPU核心或者处理器上运行。
并行的关键点在于真正的同时执行,这依赖于硬件支持,如多核处理器,多处理器系统或者特定的并行计算机硬件(如GPU)。并行可以显著提高计算效率和处理速度,特别是对于可以被分割成多个独立部分的大规模计算任务。 - 并发与并行的比较
- 基本概念:并发关注于多任务的管理和调度,使得它们在单个处理器上“看似”同时执行,并行关注于实际上在多个处理器上同时执行多个任务。
- 目标:并发的目的是提高资源的利用效率,通过任务切换来模拟同时处理多个任务;并行的目的时通过真正的同时处理来加速计算过程。
- 使用场景:并发适用于任务密集型、需要频繁交互或等待的应用场景;并行适用于计算密集型,可以被分解为多个独立子任务的应用场景。
12. 进程和线程的区别
都是系统进行资源分配和调度的基本单位。
- 进程
- 定义:进程是操作系统进行资源分配和调度的一个独立单位。每个进程都有自己的地址空间、内存、数据栈以及记录其运行轨迹的辅助数据。进程之间相互独立,一个进程崩溃不会直接影响到其他进程。
- 资源隔离:由于每个进程都有独立的空间,因此进程间的通信需要进程间通信(IPC)机制进行,如管道、消息队列、共享内存等。
- 开销:创建或销毁进程、进程间的切换等操作涉及到较大的开销,因为这些操作需要操作系统介入进行更多的资源分配和状态保存。
- 线程
- 定义:线程是进程的执行单元,是CPU调度和分配的基本单位。一个进程可以包含多个线程,他们共享进程的地址空间和资源,每个线程都有自己的执行栈和程序计数器。
- 资源共享:线程间自然地共享进程资源和数据,使得线程间的数据交换和通信更加容易。但这也意味着需要注意同步和数据一致性问题,避免出现竟态条件。
- 开销:相比进程,线程的创建、销毁和切换的开销要小得多,因为这些操作无需重复进行资源分配,只需要少量的寄存器和栈的变动。
- 区别:
- 资源分配和隔离
- 进程是资源分配的基本单位,拥有独立的内存空间,进程相互间隔;
- 线程是CPU调度的基本单位,同一进程内的线程共享内存空间和资源。
- 通信方式
- 进程间通信(IPC)需要特定的机制,成本较高;
- 线程间可以直接通过读写共享数据来通信,但需要处理同步问题。
- 创建和管理开销
- 进程的创建、销毁和切换开销较大;
- 线程的这些开销相对较小。
- 应用场景
- 进程适用于需要较大独立运行和资源保护的大型任务;
- 线程适用于轻量级任务,尤其是需要频繁共享内存数据或进行并发执行的场景。
- 资源分配和隔离
13. 什么是上下文切换
上下文切换时之操作系统在多任务处理时,由于需要保存当前任务的状态并加载下一个任务的状态而进行的切换过程。当一个任务需要暂时停止执行(如等待I/O操作完成、时间片用完等),操作系统会保存当前任务的执行环境(即上下文),然后加载下一个任务的上下文并开始执行,这个过程就是上下文切换。
- 上下文包括以下内容
- 寄存器内容:保存当前任务在CPU寄存器中的值,包括程序计数器(PC),堆栈指针(SP)等。
- 内存映像;保存当前任务在内存中的数据和代码位置。
- 进程控制块PCB:包含了进程的各种状态信息,如进程id,优先级,进程状态等。
- 上下文切换过程包括一下步骤
- 保存当前任务的上下文:将当前任务的寄存器内容、内存映像以及进程控制块保存到内存中,一边下次恢复执行。
- 加载下一个任务的上下文:从内存中读取下一个任务的寄存器内容、内存映像和进程控制块,并将其加载到CPU中。
- 切换到下一个任务:开始执行下一个任务,从其上次暂停的地方继续执行。
- 上下文切换的重要性
- 实现多任务处理:允许操作系统在单个处理器上同时运行多个任务。
- 确保任务公平调度:通过分时使用CPU资源,确保每个任务都有机会运行。
- 提高系统响应的速度:及时响应外部事件,如用户输入、硬件中断等。
- 上下文切换的开销
上下文切换虽然是必要的,但也会带来一定的开销,主要包括:- 时间开销:保存和恢复任务上下文所需的时间。
- 资源开销:频繁的上下文切换可能导致系统资源浪费。
- Cache失效:切换任务后,原任务的数据可能不再在Cache中,需要重新加载。
因此,系统设计时需要尽量减少不必要的上下文切换,如通过合理的任务调度策略、避免阻塞操作等方式来提高系统性能和效率。
14. 线程状态转换有哪些
- 新建状态:通过关键字new创建一个线程对象后,该线程就处于新建状态。
- 就绪状态:当调用start()方法启动线程后,线程进入就绪状态,等待获取CPU时间片执行任务。
- 运行状态:线程获取到CPU时间片开始执行任务,处于运行状态。
- 阻塞状态:线程因为某些原因(如等待I/O操作、获取锁失败)而暂时停止运行,进入阻塞状态。当等待的条件满足时,线程会进入就绪状态。
- 等待状态:线程调用Object.wait()、或等方法进入等待状态,需要其他线程显式唤醒才能继续执行。
- 超时等待状态:线程调用带有超时参数的等待方法(如Object.wait(long timeout))、Thread.sleep(long millis)、Thread.join(long millis)或LockSupport.parkNanos(long nanos)进入超时等待状态,一段时间后自动返回。
- 终止状态:线程执行完任务或者发生异常导致线程结束时,进入终止状态。
15. 什么是死锁
死锁是指在多线程或并发程序中,两个或多个线程无限期地等待对方释放资源的一种状态。在死锁状态下,每个线程都在等待其他线程释放资源,而导致所有线程都无法继续执行下去。
死锁通常涉及多个资源,例如内存、文件、数据库连接等。当线程之间相互竞争获取资源,并且持有自己的资源同时等待获取其他线程的资源时,就有可能发生死锁。
- 死锁发生的四个必要条件:
- 互斥条件:至少有一个资源必须处于非共享状态,即一次只能被一个进程使用。
- 占有且等待:一个进程必须占有至少一个资源,并等待获取其他线程占有的资源。
- 不可抢占:已经分配给一个进程的资源是不能被强制性的抢占,它只能被占有它的进程显式地释放。
- 循环等待:若干进程之间形成一种头尾相接的循环等待资源的关系。
16. synchronized和lock的区别
都是用于实现线程同步和互斥访问共享资源的目的。主要区别:
- 使用方式:synchronized是Java内置的关键字,可以直接在方法或代码块上使用,使用起来简单,而Lock是一个接口,需要通过具体的实现类(如ReentrantLock)创建实例,并且需要手动进行加锁和解锁的操作。
- 灵活性:Lock相对于synchronized提供了更多的灵活性,例如,Lock可以实现公平锁(按线程的请求顺序获取锁),而synchronized只能是非公平锁。此外,Lock还支持尝试获取锁、定时获取锁等功能,而synchronized没有提供这些特性。
- **性能*8:在低竞争情况下,synchronized的性能通常比Lock好,因为synchronized是由JVM底层进行优化的。在高竞争情况下,Lock的性能可能会更好,因为它提供了更细粒度的控制和更多的优化机会。
- 异常处理:使用synchronized时,如果发生异常,JVM会自动释放锁,而使用Lock时,需要手动在finally块中释放锁,以确保锁的释放,避免死锁的发生。
synchronized适用于简单的线程同步场合,使用起来更加方便,而Lock适用于复杂的同步需求,提供了更多的高级特性。
17. 什么是AQS锁
AQS(AbsreactQueueSunchronized)是Java中用于构建同步器的框架,可以用来实现各种形式的同步控制。AQS提供了一种基于FIFO等待队列的机制,通过维护一个状态变量和一个等待队列来管理同步状态和线程的竞争。
AQS是一个抽象类,它定义了两种锁:独占锁(Exclusive Lock)和共享锁(Shared Lock)。具体的同步器可以通过继承AQS并实现其中的几个方法来定义同步器,如ReentrantLock、Semaphore等都是基于AQS实现的。
AQS的核心思想是使用一个整型的state表示同步状态,当state为0时表示没有线程持有锁,大于0表示有线程持有锁。大于0表示有线程持有锁,小于0表示有线程在等待获取锁。通过对state的修改和CAS操作,实现线程的加锁和释放锁。
AQS提供了两种主要的方法:acquire和release。acquire用来获取独占锁,如果获取失败则会将当前线程加入到等待队列中;release用来释放锁,并唤醒等待队列中的线程去竞争锁。
18. 常见的AQS锁
- ReentrantLock:可重入锁,是java.util.concurent包中提供的基于AQS独占锁。它允许线程重复获取已持有的锁,同时支持公平性和非公平性两种模式。
- ReentrantReadWriteLock:可重入读写锁,有一个读锁和一个写锁组成,支持多线程同时读取共享资源,但只允许一个线程写入共享资源。
- StampedLock:是Java 8引入的一种基于乐观读写锁机制,性能较高,适用于读操作远远多于写操作的场景。
- Semaphore:信号量,基于AQS实现的共享锁,可以控制同时访问某个资源的线程数量。
- CountDownLatch:倒计时门闩,用于等待其他线程执行完特定操作后再继续执行。
- CyclicBarrier:循环屏障,用于同步多个线程,让它们在达到屏障点时互相等待,然后同时继续执行。
19. 为什么AQS锁使用双向链表
双向链表用于存储等待获取锁的线程,即等待队列。原因:
- 有序性:双向链表可以保持等待线程的先后顺序,即按照线程等待获取锁的顺寻进行排队。这样可以保证公平性,避免线程饥饿情况发生。
- 高效的插入语删除:双向链表在插入和删除节点时具有较高的效率,因为它可以通过修改前后节点的指针来完成操作,而无需像数组那样需要移动大量的元素。
- 易于管理节点状态:双向链表中的节点可以方便的记录线程的状态(比如是否被阻塞、是否已经获取锁等),这样可以更容易地管理线程在获取锁时的状态转换。
双向链表作为AQS中存储等待线程的数据结构,能够有效的保证线程等待锁的顺序,提高并发性,同时也更易于管理线程的状态,保证同步操作的正确性和高效性。
20. yield和join的区别
在Java中,yield和join都是用于线程控制的方法。
- yield()方法:
- yield()方法是一个静态方法,调用该方法会使当前正在执行的线程让出CPU,让其他具有相同优先级的线程有机会运行。
- yield()方法的作用时告诉线程调度器当前线程的执行已经完成,可以切换到其他线程执行,但并不能保证立即生效。
- 使用yield()方法可以让具有相同优先级的线程之间更均衡地分享CPU时间,但并不是强制性的。
- join()方法:
- join()方法是一个实例方法,用于让一个线程等待另一个线程完成。当在一个线程中调用另一个线程的join()方法时,当前线程会被阻塞,直到被调用的线程执行完毕。
- join()方法通常用于实现线程间的协作,比如在主线程中启动多个子线程,然后通过join()方法确保子线程都执行完毕后再执行主线程。
- join()方法还可以接受超时参数,指定最长等待时间,如果超市指定时间仍未完成,则当前线程会被唤醒。
yield()方法用于线程间的协调,让出CPU给其他线程执行;而join()方法用于线程间的协作,等待被调用线程执行完毕后再继续执行当前线程。两者都是多线程编程中的常用控制方法,用于实现不同的线程控制逻辑。
21. 线程池的七大参数
在Java中,线程池的七大参数通常是指ThreadPoolExecutor构造函数中的参数,这些参数用于配置线程池的行为。
- corePoolSize:核心线程数,指定线程池中能保持活动状态的线程数量。当提交任务时,如果核心线程数还未满,则会创建新线程执行任务。
- maximunPoolSize:最大线程数,指定线程池中允许的最大线程数两。当任务队列已满且线程池中的线程数已达到核心线程数时,如果仍有新任务提交,则会创建新线程,但不会超过最大线程数。
- keepAliveTime:线程空闲时间,即当线程池中线程数量超过核心线程数时,空闲线程的存活时间。当前线程空闲时间超过指定时间时,多余的线程会被销毁,直到线程池中的线程数量等于核心线程数。
- unit:keepAliveTime的时间单位,例如TimeUnit.SECONDS表示秒。
- workQueue:任务队列,用于存储等待执行的任务。线程池中的线程会从任务队列中获取任务并执行。
- threadFactory:线程工厂,用于创建新线程。可以自定义线程工厂来创建线程,看i如设置线程名称、优先级等。
- handler:拒绝策略,用于处理无法接受新任务的情况。当任务队列已满且线程池中的线程数已达到最大线程数时,新任务无法被处理时,会根据拒绝策略来进行处理,默认策略是异常抛出。
22. Java的内存模型
Java的内存模型(Java Memory Model, 简称JMM)定义了Java程序在多线程环境下的内存访问规则和行为。它确保了线程之间的可见性、有序性和原子性。
Java的内存模型中一些重要概念如下:
- 主内存:
- 主内存是所有线程共享的内存区域,包含了程序中的变量数据。
- 所有变量的读写操作都要经过主内存来完成。
- 工作内存:
- 每个线程都有自己的工作内存,用于存储变量的副本。
- 工作内存是线程独占的,其他线程无法直接访问。
- 内存间的相互交换:
- 线程对变量的读写操作都是在工作内存中进行的。
- 在读取变量时,需要将主内存中的值拷贝到工作内存中。
- 在写入变量时,需要将工作内存中的值刷新到主内存中。
- 内存屏障:
- 内存屏障是指令序列,用于控制指令的执行顺序。
- 内存屏障可以保证特定的内存操作顺序,例如读取操作优先于写入操作。
- 内存屏障可以防止指令重新排序和优化。
- 原子操作:
-
原子操作是不可被中断的单个操作,要么完全执行,要么完全不执行。
-
JMM保证了基本数据类型的读写操作是原子性的。
-
对于复合操作,可以使用synchronized、volatile或java.util.concurrent.atomic包中的原子类来确保原子性。
-
在写入变量时,需要将工作内存中的值刷新到主内存中。
-
- 内存屏障:
- 内存屏障是指令序列,用于控制指令的执行顺序。
- 内存屏障可以保证特定的内存操作顺序,例如读取操作优先于写入操作。
- 内存屏障可以防止指令重新排序和优化。
- 原子操作:
- 原子操作是不可被中断的单个操作,要么完全执行,要么完全不执行。
- JMM保证了基本数据类型的读写操作是原子性的。
- 对于复合操作,可以使用synchronized、volatile或java.util.concurrent.atomic包中的原子类来确保原子性。
Java内存模型提供了一种抽象的并发编程模型,通过规定内存访问规则和行为,确保了多线程程序的正确性和可靠性。
标签:面试题,java,2024,任务,线程,内存,进程,操作,执行 From: https://www.cnblogs.com/mochen1025/p/18207006