多线程排期-包括了word文档和面试问题
多线程关键字-多个cpu资源和多个线程;
线程对应任务,进程对应程序。
private static Condition condition = lock.newCondition();
条件变量调用方法await()和signal(),等待和唤醒
执行顺序,调度,线程资源的分配。
线程池-队列
关于threadLocal维护类型的成员变量,存储资源对象。
在多线程面试中,面试官可能会问到以下方面的问题:
1.多线程基础知识:如什么是线程、进程和线程的区别、线程的生命周期等。
会。 生命周期有很多种状态:新建lock,调用什么方法进入就绪,运行获得cpu资源对应什么方法,阻塞,等待调用的方法,超时等待,两种情况(好的坏的)导致终止。
2.线程创建方式:如何创建线程、实现Runnable接口和继承Thread类的区别。
会。 区别一个是共享数据,一个是继承的情况。
3.线程同步与互斥:如何实现线程同步、使用synchronized关键字和Lock接口的区别。
会。性能。可中断性。
4.线程通信:如何实现线程间通信、wait()、notify()、notifyAll()的作用。
生产后唤醒等待的。消费后唤醒等待的
5.线程池:线程池的作用、如何创建线程池、常用的线程池参数设置。
通过设置不同的参数,可以根据实际情况来调整线程池的行为,以满足不同场景下的需求。
作用:减少线程的创建和开销提高系统性能。控制并发线程数量。避免资源过度利用。限制并发线程数量。
控制并发数量。
创建方式:
newCachedThreadPool():创建一个可缓存的线程池,线程数量根据任务的数量动态调整。
newFixedThreadPool(int nThreads):创建一个固定大小的线程池,线程数量固定为指定的数量。
newSingleThreadExecutor():创建一个单线程的线程池,保证任务按顺序执行。
ThreadPoolExecutor类:ThreadPoolExecutor是Java中用于自定义线程池的类,通过构造函数可以指定线程池的核心线程数、最大线程数、线程存活时间等参数,更灵活地创建线程池。
ScheduledThreadPoolExecutor类:ScheduledThreadPoolExecutor是ThreadPoolExecutor的子类,用于创建可以执行定时任务的线程池,可以通过schedule()和scheduleAtFixedRate()等方法执行定时任务
newSingleThreadScheduledExecutor:创建一个单线程执行程序,它可安排在给定延迟后运行命令或者定期执行
newScheduledThreadPool:创建一个线程池,它可安排在给定延迟后运行命令或者定期的执行
核心线程数(Core Pool Size):线程池中保持活动的最小线程数。当任务数量超过核心线程数时,线程池会根据其他参数来决定是否创建新的线程来处理任务。
最大线程数(Maximum Pool Size):线程池中允许的最大线程数。当任务数量超过核心线程数且工作队列已满时,线程池会创建新的线程来处理任务,但不会超过最大线程数。
空闲线程存活时间(Keep Alive Time):当线程池中的线程数量超过核心线程数时,多余的空闲线程会在经过一定时间后被回收。
任务队列(Work Queue):用于存储等待执行的任务的队列。常见的工作队列包括有界队列(如ArrayBlockingQueue)和无界队列(如LinkedBlockingQueue)等。
ArrayBlockingQueue数组和链表
6.并发工具类:如何使用并发工具类如CountDownLatch、CyclicBarrier、Semaphore等。
CountDownLatch:用于等待多个线程完成某项任务。
CyclicBarrier:用于让一组线程在某个点上相互等待,可以重复使用。
Semaphore:用于控制同时访问某个特定资源的线程数量。
计数器的初始值为等待的事件数量。每当一个线程完成其任务时,它会调用 countDown() 方法来减少计数器的值。当计数器的值降到零时,所有等待的线程将被释放,继续执行。
构造方法CountDownLatch(int count):创建一个 CountDownLatch,初始计数为 count。主线程等待,知道计数器为0
关键字计数器
CyclicBarrier允许一组线程互相等待,直到所有线程都到达某个公共屏障点。它可以重用,因此称为“循环屏障”。
CyclicBarrier(int parties):创建一个 CyclicBarrier,指定需要等待的线程数量。调用方法await()等待
关键字屏障点
Semaphore用于控制同时访问某个特定资源的线程数量。它可以用于限制并发访问的线程数量。
7.线程安全性:什么是线程安全、如何保证线程安全、常见的线程安全性问题。
什么是线程安全?
在并发编程中,保证线程安全性是至关重要的,以避免数据竞争、死锁、数据损坏等问题。
如何保证线程安全?
加锁。concurrentmap,原子操作:使用原子类(如 AtomicInteger、AtomicLong 等)来保证特定操作的原子性,避免数据竞争。
它使用了分段锁(Segment)来提高并发性能,不同的段可以由不同的线程同时访问,从而减少锁的竞争。在读取和写入操作时,只会锁定相关的段,而不是整个集合。
两个线程同时对 AtomicInteger 和 AtomicLong 进行增加操作。incrementAndGet() 方法是原子操作,确保在多线程环境下的安全性。
常见的安全性问题?
死锁。数据损坏。竞态条件
活跃性问题:包括死锁、饥饿和活锁等问题,导致某些线程无法继续执行或无法取得进展。
8.线程调度:线程调度的机制、线程的优先级、yield()和sleep()方法的区别。
线程调度是操作系统对多个线程进行管理和分配处理器资源的过程。线程调度的机制包括抢占式调度和协作式调度
线程的优先级用于确定线程获取处理器资源的顺序
yield() 方法是一个线程调度器的方法,它会让出当前线程的执行权,使其他具有相同或更高优先级的线程有机会执行。
他会暂停执行,并处于就绪。等待调度
sleep() 方法是让当前线程暂停执行一段时间。当一个线程调用 sleep() 方法时,它会进入阻塞状态,
9.线程死锁:什么是死锁、如何避免死锁、常见的死锁情况和解决方法。
常见的死锁情况
互斥条件:
至少有一个资源必须处于非共享模式,即一个资源只能被一个线程使用。
持有并等待:
一个线程持有至少一个资源,并等待获取其他资源。
不可剥夺:
资源不能被强制剥夺,只有在线程完成其任务后才能释放资源。
循环等待:
存在一种线程资源的循环等待关系,即线程 A 等待线程 B 持有的资源,线程 B 等待线程 C 持有的资源,直到线程 D 等待线程 A 持有的资源。
死锁解决方法
资源剥夺:
在必要时,可以强制剥夺某个线程的资源,将其释放给其他线程。
线程重启:
如果检测到死锁,可以选择终止某些线程并重启它们,以打破死锁状态。
重试机制:
在发生死锁时,可以让线程在一段时间后重试获取资源,直到成功为止。
10.Java并发包:Java提供的并发包有哪些、如何使用Concurrent包中的类。
以上是在多线程面试中可能会问到的一些问题,准备面试时建议对这些知识点进行深入理解和准备。
外加24总计35个问题半小时一个问题,预计总共6h小时完成,还需5h===15点37分-16:37完成两个。总共到21:00完成所有问题!!!
19、如何确定核心线程池呢?
IO密集型任务:推荐:核心线程数大小设置为2N+1 (N为计算机的CPU核数)
CPU密集型任务:推荐:核心线程数大小设置为N+1 (N为计算机的CPU核数)
20、为什么不建议使用Executors创建线程池呢?
主要原因是如果使用Executors创建线程池的话,它允许的请求队列默认长度是Integer.MAX_VALUE,这样的话,有可能导致堆积大量的请求,从而导致OOM(内存溢出)。
23、谈谈你对ThreadLocal的理解
ThreadLocal 主要功能有两个,第一个是可以实现资源对象的线程隔离,让每个线程各用各的资源对象,避免争用引发的线程安全问题,第二个是实现了线程内的资源共享
24、ThreadLocal的底层原理实现了解吗?
在ThreadLocal内部维护了一个一个 ThreadLocalMap 类型的成员变量,用来存储资源对象
当我们调用 set 方法,就是以 ThreadLocal 自己作为 key,资源对象作为value,放入当前线程的 ThreadLocalMap 集合中
当调用 get 方法,就是以 ThreadLocal 自己作为 key,到当前线程中查找关联的资源值
当调用 remove 方法,就是以 ThreadLocal 自己作为 key,移除当前线程关联的资源值
join和wait
join() 方法
join() 方法是一个实例方法,用于等待线程终止。当一个线程调用另一个线程的 join() 方法时,它会被阻塞,直到另一个线程执行完毕。
主要用于在当前线程中等待其他线程执行完毕,然后再继续执行。
wait() 方法
wait() 方法是一个实例方法,用于线程间的协作和同步。当一个线程调用对象的 wait() 方法时,它会释放对象的锁,并进入等待状态,直到其他线程调用相同对象的 notify() 或 notifyAll() 方法来唤醒它。
主要用于线程间的通信和协作,通常与 notify() 或 notifyAll() 方法配合使用,实现线程间的同步。
7、如何停止一个正在运行的线程呢?
第一:可以使用退出标志,使线程正常退出,也就是当run方法完成后线程终止,一般我们加一个标记 这个标记得用volitale变量修饰
第二:可以使用线程的stop方法强行终止,不过一般不推荐,这个方法已作废
第三:可以使用线程的interrupt方法中断线程,内部其实也是使用中断标志来中断线程
谈谈对volitale的理解吧
可见性:volatile关键字保证了变量的修改对其他线程是可见的,即一个线程对volatile变量的修改对其他线程是立即可见的。
禁止指令重排序:volatile关键字可以防止指令重排序,确保变量的修改顺序符合预期。这里我个人可以理解为对性能的优化不会影响变量的资源分配和顺序操作等。
不保证原子性:虽然volatile能够保证可见性,但并不能保证对变量的操作是原子性的,如果有多个线程同时对volatile变量进行修改,仍然可能发生竞态条件。
这里个人理解为对volatile关键字保证了变量的修改对其他线程是可见的,重点就是可见性。
这里在编译器或者cpu提高性能的时候可能会对指令进行重排序,这种重排序在单线程环境下不会产生问题,但在多线程环境下可能会导致程序出现意料之外的行为。为了避免这种情况,Java中提供了volatile关键字,其中的一个作用就是禁止指令重排序。这里的重点就是线程的安全性。
synchronized的底层原理?
synchronized关键字的原理
1.当一个线程进入一个synchronized方法或代码块时,它会尝试获取对象的Monitor(即对象锁)。
2.如果Monitor的计数器为0,表示该对象没有被其他线程持有,当前线程可以成功获取锁,并将计数器设置为1。
3.如果Monitor的计数器不为0,表示该对象已经被其他线程持有,当前线程会进入对象的等待队列中等待。
4.当持有锁的线程执行完synchronized块,会释放锁,并唤醒等待队列中的一个线程来竞争锁。
ReentrantLock?
就是可重入锁。 内部直接增加重入次数 就行了,标识这个线程已经重复获取一把锁而不需要等待锁的释放。
这个 锁跟synchronized关键字一样,都是悲观锁。
啥是悲观锁?啥是乐观锁。
悲观锁的思想是: 在整个操作过程中,认为数据很可能会被其他线程修改,因此在访问数据之前先获取锁,以防止其他线程对数据进行修改。
这里理解就是访问资源前要先获取锁,防止其他线程该资源,所以容易阻塞。
写多读少
乐观锁的思想是: 在整个操作过程中,认为数据很少被其他线程修改,因此不加锁直接进行操作,但在更新数据时会进行版本号或时间戳等的检查。
这里用无锁编程技术,CAS操作 就很适用于读多写少的情况。
CAS和AQS?
CAS就是一种乐观锁的思想。CAS的基本思想就是:先比较当前的值和期望的值是否相等,如果相等,就进行更新;如果不相等,就不更新,而是重新尝试。这样可以保证在并发情况下,多个线程对同一个值进行操作时,不会发生冲突,保证了操作的原子性和一致性。
重点就是比较,改值。保证操作的原子性和一致性。
比如保安。
AQS
创建一个新的类,继承 AbstractQueuedSynchronizer。
实现必要的方法:根据需要实现以下方法:
tryAcquire(int arg):尝试获取锁,如果成功返回 true,否则返回 false。
tryRelease(int arg):尝试释放锁,返回 true 表示释放成功,false 表示失败。
tryAcquireShared(int arg):尝试以共享模式获取资源,返回值表示获取的结果。
tryReleaseShared(int arg):尝试以共享模式释放资源。
定义状态:可以使用 getState() 和 setState(int newState) 方法来管理同步器的状态。
使用 CAS:可以使用 compareAndSetState(int expect, int update) 来实现无锁的状态更新。
是阻塞式锁和相关的同步器工具的框架。内部有一个属性 state 属性来表示资源的状态,默认state等于0,表示没有获取锁,state等于1的时候才标明获取到了锁。通过cas 机制设置 state 状态在它的内部还提供了基于 FIFO 的等待队列,是一个双向列表,其中tail 指向队列最后一个元素,head 指向队列中最久的一个元素。
21、如果控制某一个方法允许并发访问线程的数量?
在jdk中提供了一个Semaphore类(信号量),它提供了两个方法,semaphore.acquire() 请求信号量,可以限制线程的个数,是一个正数,如果信号量是-1,就代表已经用完了信号量,其他线程需要阻塞了
第二个方法是semaphore.release(),代表是释放一个信号量,此时信号量的个数+1