多线程
- 0、什么是线程和进程的区别?
- 1、如何创建线程?有几种方式?
- 2、线程的生命周期是怎样的?包括哪些状态?
- 3、什么是线程池?为什么使用线程池?
- 4、Java中的线程调度是如何工作的?
- 5、什么是上下文切换?
- 6、什么是线程的上下文ClassLoader?它的作用是什么?
- 7、线程同步的方法有哪些?如何避免线程安全问题?
- 8、什么是死锁?如何避免死锁?
- 9、Java中的锁有哪些?如何使用锁进行线程同步?
- 10、什么是线程安全?如何实现线程安全?
- 11、什么是线程间通信?有哪些方法实现线程间通信?
- 12、如何在多个线程之间共享数据?
- 13、什么是线程局部变量?如何使用线程局部变量?
- 14、什么是守护线程?它们与用户线程有什么区别?
- 15、什么是线程优先级?如何设置线程优先级?
- 16、什么是线程安全的集合类?列举一些线程安全的集合类。
- 17、什么是可重入锁?为什么要使用可重入锁?
- 18、什么是阻塞队列?它有什么作用?
- 19、用java实现阻塞队列
- 20、什么是线程组?如何使用线程组?
- 21、Java中的并发容器有哪些?它们分别适用于什么场景?
- 22、什么是线程的中断?如何中断一个线程?
- 23、Java 中 interrupted 和 isInterrupted 方法的区别?
- 24、什么是线程的异常处理机制?如何处理线程中的异常?
- 25、如何控制线程的执行顺序?有哪些方式可以实现线程的协调和顺序执行?
- 26、Java中的volatile关键字的作用是什么?
- 27、乐观锁、悲观锁
- 28、在 java 中 wait 和 sleep 方法的不同?
- 29、怎么检测一个线程是否拥有锁?
- 30、Thread 类中的 yield 方法有什么作用?
- 31、Java 线程池中 submit() 和 execute()方法有什么区别?
- 32、什么是线程调度器(Thread Scheduler)和时间分片(TimeSlicing )?
- 33、同步方法和同步块,哪个是更好的选择?
- 34、 线程池中提交一个任务的流程是怎样的?
- 35、如何优雅的停掉一个线程?
- 36、线程池分为哪几种,分别如何创建?
- 37、什么是CAS?
- 38、CAS和锁的区别是什么?
- 39、CAS在什么场景常用?
- 40、 ThreadLocal是什么,有什么作用?
0、什么是线程和进程的区别?
进程是执行过程中的一个实例。
线程是进程内的一个独立的执行序列。
主要区别:
1、资源占用:创建和销毁进程开销比线程大。
2、通信和同步:进程间通讯较为复杂,如管道、消息队列等。
线程间可以直接读取和共享数据,通信更简单。
3、并发性:多个线程可以在同一个进程中同时执行。
4、容错性:进程间相互隔离,一个进程崩溃不会影响其他进程,但是一个线程崩溃有可能使整个进程崩溃。
1、如何创建线程?有几种方式?
1、继承Thread类,重写run方法。调用start()方法启动。
public class MyThread extends Thread {
@Override
public void run(){
//线程执行逻辑
}
public static void main(String[] args){
MyThread thread = new MyThread();
thread.start(); // 启动线程
}
}
2、实现Runnable接口。实现run()方法,调用start()方法启动。
public class MyRunnable implements Runnable {
@Override
public void run() {
// 线程的执行逻辑
}
public static void main(String[] args) {
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start(); // 启动线程
}
}
}
3、实现Callable接口。
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 在这里定义线程要执行的任务逻辑
// 并返回一个结果
return 42;
}
}
4、利用线程池创建线程。
public class testThread implements Runnable{
public static void main(String[] args) throws ExecutionException,InterruptedException(){
ExecutorService eService = Exectors.newFixedThreadPool(10);
eService.execute(new testThread());
}
public void run(){
sout("hello");
}
}
2、线程的生命周期是怎样的?包括哪些状态?
1、新建状态(NEW):线程被创建,尚未启动,未分配系统资源。
2、可运行状态(Runnable):已经分配了资源,可以start进入运行状态。
3、运行状态(Running):run()方法正在执行。
4、阻塞状态(Blocked):调用了sleep()方法。
5、等待状态(Waiting): 调用wait()方法,或者join()方法进入。需要等待notyfy()或notifyAll()方法唤醒。
6、计时等待状态(Timed Waiting):调用sleep或者wait进入该状态,带有超时参数,等待一段时间后会自动唤醒。
7、终止状态(Terminated):线程的run方法执行完毕或被终止。
3、什么是线程池?为什么使用线程池?
线程池是一种线程的管理机制,预先创建一组线程,将任务提交给这些线程执行。线程池中的线程可以重复利用。提高线程的利用率和系统性能。
使用线程池主要用于解决以下集中问题:
1、减少线程创建和销毁的开销。
2、并发控制线程数量:可以限制系统中并发执行的线程数量,避免过多的线程占用系统资源。
3、提供线程管理和监控功能:可以设置线程池大小,核心线程数,线程空闲时间等。
4、Java中的线程调度是如何工作的?
线程调度是由操作系统的线程调度器负责的。java的线程调度器在底层和操作系统的线程调度机制交互,决定哪个线程在给定的时间片内执行。
5、什么是上下文切换?
上下文切换是指在多线程环境下,由于线程调度器决定切换到下一个线程执行,当前正在执行的线程需要保存自己的上下文,并将控制权切换给下一个要执行的线程,同时恢复其上下文。
(上下文切换有点像我们同时阅读几本书,在来回切换书本的同时我们需要记住每本书当前读到的页码。在程序中,上下文切换过程中的“页码”信息是保存在进程控制块(PCB)中的。)
上下文切换开销较大。
上下文切换的发生场景包括:
1、当前线程执行完毕,调度器选择下一个要执行的线程。
2、当前线程让出CPU执行权,如调用yield()方法。
3、当前线程被更高优先级线程占用。
6、什么是线程的上下文ClassLoader?它的作用是什么?
每个线程都有一个上下文,用于加载线程在执行过程中所需要的类和资源。
作用:
Ⅰ、类加载器委托机制的支持,一个类加载器加载类时先让弗雷加载,加载不到,再由自身在家。
Ⅱ、动态加载。
Ⅲ、资源访问。线程的上下文ClassLoader可以影响资源的加载。
7、线程同步的方法有哪些?如何避免线程安全问题?
实现线程同步有很多方法:
1、使用synchronized关键字,加在方法或代码块前。保证同一时间只有一个线程可以执行。
2、使用ReentrantLock类,它是java提供的可重入锁的实现类。使用lock方法获取锁,unlock方法可以释放锁。
3、使用volatile关键字。可以保证变量的可见性,当线程改了变量的值之后,其他线程可以立即看到修改后的值。
4、使用同步容器类。Vector,HashTable,CurrentHashMap等等,这些都是线程安全的。
为了避免线程安全,可以采用以下策略:
1、使用线程安全的数据结构:
2、使用同步机制:通过synchronized关键字、ReentrantLock等同步机制来保护共享资源的访问。
3、避免共享数据:将数据封装在对象内部。每个线程操作自己的对象实例。
4、合理使用线程安全的设计模式:使用线程安全的设计模式,如Immutable、Singleton、Guarded Suspension等,来确保多线程环境下的安全性。
8、什么是死锁?如何避免死锁?
死锁是在多线程中,两个或多个线程被永久的阻塞,导致程序无法继续执行。
死锁发生的四个必要条件:
1、互斥条件:一个资源只能被一个线程持有。
2、占有且等待条件:线程持有至少一个资源,且等待获取其他资源。
3、不可抢占条件:资源只能在线程资源释放时才能被其他线程获取。
4、循环等待:每个线程都在等待下一个线程持有的资源。
如何避免死锁:
Ⅰ、避免使用多个锁。
Ⅱ、破坏循环等待条件,对资源进行排序,使线程按序请求。
Ⅲ、使用超时机制:设置超时时间。
Ⅳ、死锁检测与恢复:监控线程状态和资源分配情况,有死锁即终止线程或回滚。
9、Java中的锁有哪些?如何使用锁进行线程同步?
1、synchronized关键:
Java中最基本的锁机制。它可以用于方法级别的同步或块级别的同步。
使用synchronized关键字修饰的方法或代码块在同一时间只允许一个线程执行。
2、ReentrantLock类:
ReentrantLock是一个可重入锁,比synchronized更加灵活,
使用
ReentrantLock lock = new ReentrantLock();
lock.lock(); // 获取锁
try {
// 线程安全的代码
} finally {
lock.unlock(); // 释放锁
}
3、ReadWriteLock接口:
ReadWriteLock供了一种读写分离的锁机制。它允许多个线程同时读取共享数据,但只允许一个线程写入数据。
ReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLock().lock(); // 获取读锁
try {
// 线程安全的读取代码
} finally {
lock.readLock().unlock(); // 释放读锁
}
lock.writeLock().lock(); // 获取写锁
try {
// 线程安全的写入代码
} finally {
lock.writeLock().unlock(); // 释放写锁
}
10、什么是线程安全?如何实现线程安全?
线程安全是指当多个线程同时访问共享资源时,不会引发任何数据不一致或不正确的情况。
实现方法:
Ⅰ、使用互斥锁保护共享资源。一次只允许一个线程获取锁。
Ⅱ、原子操作。
Ⅲ、使用线程安全的数据结构,Vector、ConcurrentHashMap等
Ⅳ、使用不可变对象存储共享数据。
11、什么是线程间通信?有哪些方法实现线程间通信?
线程通信是指多个线程之间进行信息交流和数据传递的过程。
不同线程可能需要共享数据,协调任务执行顺序或者通知等操作。
常见线程间通信方法:
Ⅰ、共享变量,
Ⅱ、锁机制,持有锁的线程可以执行临近区的代码。
Ⅲ、条件变量,
Ⅳ、信号量,也是一种计数器,线程通过信号量的等待操作申请资源,通过释放操作释放资源。
Ⅴ:阻塞队列,线程想阻塞队列插入元素,或者取出元素通信。
Ⅵ:管道,一个线程写数据,另一个线程可以读数据。
12、如何在多个线程之间共享数据?
Ⅰ、synchronized、volatile关键字
public void someMethod() {
// 非同步的代码
synchronized (sharedObject) {
// 同步的代码
}
// 非同步的代码
}
// volatile 关键字声明一个变量,确保对该变量的读写操作都是直接从主内存进 // 行的,而不是从线程的本地缓存读取。
private volatile int sharedVariable;
Ⅱ、使用共享变量
Ⅲ、使用线程安全的数据结构
Ⅳ、使用阻塞队列
13、什么是线程局部变量?如何使用线程局部变量?
线程局部变量是一种特殊类型的变量,它被限定在每个线程的独立副本中,每个线程都有自己独立的变量实例,线程之间互不干扰。
使用线程局部变量的目的是为了实现线程安全,避免线程键数据竞争。
使用ThreadLocal类实现线程的局部变量。如何使用?
Ⅰ、创建一个`ThreadLocal`实例
Ⅱ、在需要使用线程局部变量的线程中,通过`set()`方法设置变量的值
Ⅲ:在需要获取线程局部变量的地方,使用`get()`方法获取变量的值
Ⅳ、当线程不再需要使用线程局部变量时,可以通过`remove()`方法将其移除
示例:
public class ThreadLocalExample {
private static ThreadLocal<Integer> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
// 创建并启动两个线程
Thread thread1 = new Thread(() -> {
threadLocal.set(10); // 设置线程局部变量的值
System.out.println("Thread 1: " + threadLocal.get()); // 获取线程局部变量的值
threadLocal.remove(); // 移除线程局部变量
});
Thread thread2 = new Thread(() -> {
threadLocal.set(20); // 设置线程局部变量的值
System.out.println("Thread 2: " + threadLocal.get()); // 获取线程局部变量的值
threadLocal.remove(); // 移除线程局部变量
});
thread1.start();
thread2.start();
}
}
14、什么是守护线程?它们与用户线程有什么区别?
为其他线程提供服务,它的存在不会阻止程序的终止。所有用户线程结束收,守护线程也会终止。
和用户线程的区别如下:
Ⅰ、生命周期:当所有的用户线程结束后,守护线程会被自动终止。而用户线程则会继续执行,直到其执行完毕或主动调用了终止方法。
Ⅱ、对JVM的影响:当只剩下守护线程在运行时,JVM会自动退出。
Ⅲ、创建方式,setDameon(boolean on)设置线程为守护线程。
Ⅳ、线程默认是用户线程,设置serDameon(true)才为守护线程。
15、什么是线程优先级?如何设置线程优先级?
线程优先级是指定线程相对于其他线程的执行优先级的属性。其中线程优先级范围由低到高是1-10,所有线程默认都是5。
setPriority(int priority)方法可以设置优先级。
示例代码:
Thread thread = new Thread(() -> {
// 线程任务
});
// 设置线程优先级为最高
thread.setPriority(Thread.MAX_PRIORITY);
// 启动线程
thread.start();
16、什么是线程安全的集合类?列举一些线程安全的集合类。
线程安全的集合类是指在多线程环境下使用时,能够确保数据的一致性和线程安全性的集合类。
ConcurrentHashMap
17、什么是可重入锁?为什么要使用可重入锁?
可重入锁是一种特殊的锁机制,也称为递归锁。它允许同一个线程多次获取同一个锁,而不会发生死锁。
使用原因:
Ⅰ、避免死锁。
Ⅱ、简化编程模型。在复杂的代码结构中,可能存在多个方法互相调用的情况。
如果不使用可重入锁,就需要在代码中手动处理锁的获取和释放,增加了编程的复杂性和出错的可能性。
Ⅲ、提高性能。避免了频繁的切换线程。
18、什么是阻塞队列?它有什么作用?
阻塞队列提供了线程安全的插入和删除操作。队列为空或者为满的时候是阻塞的。通过使用阻塞队列,生产者线程可以将数据插入队列,即使队列已满,生产者线程也会被阻塞,直到队列有空间可以插入。消费者线程可以从队列中获取数据,即使队列为空,消费者线程也会被阻塞,直到队列中有可用的元素。
作用:
Ⅰ、线程安全。
Ⅱ、阻塞操作,队列为空或者为满的时候是阻塞的。可以避免出现不必要的轮询或者等待,提高了效率。
Ⅲ、限流和流量控制,可以限制任务的并发数量。
19、用java实现阻塞队列
package com.wb.demo;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class BlockingQueueExample {
// 创建一个容量为10的阻塞队列
private static BlockingQueue<Integer> blockingQueue = new ArrayBlockingQueue<>(10);
public static void main(String[] args) {
// 创建生产者线程和消费者线程
Thread producerThread = new Thread(new Producer());
Thread consumerThread = new Thread(new Consumer());
// 启动线程
producerThread.start();
consumerThread.start();
}
static class Producer implements Runnable {
@Override
public void run() {
try {
for (int i = 1; i <= 20; i++) {
// 将元素添加到队列中
blockingQueue.put(i);
System.out.println("Produced: " + i);
// 为了展示阻塞效果,生产者线程休眠一段时间
Thread.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class Consumer implements Runnable {
@Override
public void run() {
try {
for (int i = 1; i <= 20; i++) {
// 从队列中获取元素
int num = blockingQueue.take();
System.out.println("Consumed: " + num);
// 为了展示阻塞效果,消费者线程休眠一段时间
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
20、什么是线程组?如何使用线程组?
线程组(Thread Group)是一种用于组织和管理线程的机制。
主要作用:
Ⅰ、线程组可以将相关的线程进行逻辑分组,便于管理和组织。
例如,可以将相同功能或相似任务的线程放入同一个线程组中,便于统一管理和监控。
Ⅱ、可以对一组线程进行统一的操作。批量启动,批量停止,中断
Ⅲ、可以集中处理异常。
使用:
public class ThreadGroupExample {
public static void main(String[] args) {
ThreadGroup group = new ThreadGroup("MyThreadGroup");
Thread thread1 = new Thread(group, new MyRunnable(), "Thread1");
Thread thread2 = new Thread(group, new MyRunnable(), "Thread2");
thread1.start();
thread2.start();
// 打印线程组中的活动线程数
System.out.println("Active Threads: " + group.activeCount());
// 停止线程组中的所有线程
group.interrupt();
}
static class MyRunnable implements Runnable {
public void run() {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + " finished");
} catch (InterruptedException e) {
// 处理线程中断异常
System.out.println(Thread.currentThread().getName() + " interrupted");
}
}
}
}
21、Java中的并发容器有哪些?它们分别适用于什么场景?
Ⅰ、ConcurrentHashMap:线程安全的哈希表,适用于多线程同时读写的场景。
Ⅱ、阻塞队列。
22、什么是线程的中断?如何中断一个线程?
线程中断是用于请求目标线程停止正在执行的任务。中断并不会强制终止线程,而是向目标线程发出一个中断信号。
线程中孤单通过interrupt()方法出发。当一个线程调用另一个线程的interrupt()
方法时,会将目标线程的中断状态设置为"中断"。
线程可以通过以下方式中断请求:
Ⅰ、检查中断状态:使用isInterrupted()方法。
Ⅱ、抛出InterruptedException,线程可以捕获该异常,并在异常处理中进行适当的操作,例如释放资源、终止任务等。
23、Java 中 interrupted 和 isInterrupted 方法的区别?
interrupt 方法用于中断线程。调用该方法的线程的状态为将被置为”中断”状态。
interrupted查询当前线程的中断状态,并且清除原状态。如果一个线程被中断了,第一次调用 interrupted 则返回 true,第二次和后面的就返回 false 了。
isInterrupted仅仅是查询当前线程的中断状态
24、什么是线程的异常处理机制?如何处理线程中的异常?
异常处理机制就是,捕获处理在线程执行过程中抛出的异常。
方法:
Ⅰ、Thread.UncaughtExceptionHandler接口,通过setDefaultUncaughtExceptionHandler方法可以设置默认的未捕获异常处理器。
Ⅱ、try-catch.
Ⅲ、Callable接口,可以通过submit()方法提交任务,会得到一个Future对象,可以用于检查任务是否完成,获取任务执行结果和任务抛出的异常。
25、如何控制线程的执行顺序?有哪些方式可以实现线程的协调和顺序执行?
Ⅰ、使用join方法。
Ⅱ、使用wait(),和notify(),notifyAll()。
Ⅲ、使用Lock和Condition接口。
// 创建一个lock对象
Lock lock = new ReentrantLock();
// 创建多个Condition对象,控制线程的等待和唤醒。
Condition conditionA = lock.newCondition();
Condition conditionB = lock.newCondition();
// 在线程中获取锁,进入临界区:
lock.lock();
try {
// 线程执行的逻辑
} finally {
lock.unlock(); // 释放锁
}
// 使用 await() 方法让线程等待某个条件:
lock.lock();
try {
while (!conditionMet()) {
conditionA.await(); // 线程等待条件满足
}
// 执行线程的后续操作
} finally {
lock.unlock();
}
// 使用 signal() 或 signalAll() 方法唤醒等待的线程
try {
// 改变条件,通知等待的线程
conditionA.signal(); // 唤醒一个等待线程
conditionB.signalAll(); // 唤醒所有等待线程
} finally {
lock.unlock();
}
26、Java中的volatile关键字的作用是什么?
主要是保证被修饰的变量在多线程环境下的可见性。
当一个共享变量被 volatile 修饰时,它会保证修改的值会立即被更新到主存,
当有其他线程需要读取时,它会去内存中读取新值。
27、乐观锁、悲观锁
悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。
乐观锁:每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,乐观锁适用于多读的应用类型,这样可以提高吞吐量。
如何实现悲观锁:
class PessimisticLockExample {
private int data;
private final Object lock = new Object();
public void updateData(int newData) {
synchronized (lock) {
// 更新数据
data = newData;
System.out.println("Data updated successfully.");
}
}
}
如何实现乐观锁:
class OptimisticLockExample {
private int data;
private int version;
public void updateData(int newData) {
// 读取数据和版本号
int currentData = data;
int currentVersion = version;
// 模拟其他线程修改数据
simulateConcurrentModification();
// 更新数据时检查版本号
if (currentVersion == version) {
data = newData;
version++;
System.out.println("Data updated successfully.");
} else {
System.out.println("Data update failed due to concurrent modification.");
}
}
private void simulateConcurrentModification() {
// 模拟其他线程修改数据
// 这里可以添加一些延时或其他操作来模拟并发场景
}
}
28、在 java 中 wait 和 sleep 方法的不同?
wait()方法使Object类定义的,用于线程间的同步和通信。
线程执行wait()方法使,会释放锁持有的锁,进入等待状态,直到其他线程调用先按沟通对象的notify()或notifyAll()方法唤醒线程。
synchronized (lock) {
while (!condition) {
try {
lock.wait(); // 线程进入等待状态
} catch (InterruptedException e) {
// 处理异常
}
}
// 执行需要等待的操作
}
sleep()方法是Thread类定义的,是当前线程暂停执行一段时间,暂时会释放CPU资源,但不会释放锁。达到指定时间会重新进入就绪状态,等待CPU执行时间片。
try {
Thread.sleep(1000); // 线程暂停1秒钟
} catch (InterruptedException e) {
// 处理异常
}
29、怎么检测一个线程是否拥有锁?
在 java.lang.Thread 中有一个方法叫 holdsLock(),它返回 true 如果当且仅当当前线程拥有某个具体对象的锁。
30、Thread 类中的 yield 方法有什么作用?
使当前线程从执行状态(运行状态)变为可执行态(就绪状态)。
class MyThread extends Thread {
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + ": " + i);
// 当 i 等于 2 时,调用 yield()方法
if (i == 2) {
Thread.yield();
}
}
}
}
public class Main {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
}
}
以上代码可能出现不同的结果。yield()方法调用可能使线程切换,使两个线程交替执行。但是,线程调度有不确定性,结果可能有所不同。
31、Java 线程池中 submit() 和 execute()方法有什么区别?
两个方法都可以向线程池提交任务,不同点:
Ⅰ、返回值类型: submit()方法返回一个**Future**对象,可以用于获取任务的执行结果。而**execute()**方法没有返回值。
Ⅱ、异常处理:submit()方法可以捕获任务执行过程中抛出的异常,可以通过Future对象的get()方法获取异常。execute()方法无法捕获任务执行过程中的异常信息。
Ⅲ、方法重载:submit()方法由多种重载形式,可以接受`Runnable`或`Callable`类型的任务,以及返回结果的`Runnable`或`Callable`类型的任务。而`execute()`方法只接受`Runnable`类型的任务。
Ⅳ、返回结果:通过**submit()**方法提交的**Callable**任务可以返回执行结果。**execute()**方法提交的**Runnable**任务没有返回结果。
submit()方法更加灵活,可以接受不同类型的任务并获取执行结果。
execute()方法更加简单,只能接受Runnable类型的任务,没有返回结果。
32、什么是线程调度器(Thread Scheduler)和时间分片(TimeSlicing )?
线程调度器:
协调和管理计算机系统中的线程执行,决定线程在处理器上的执行顺序,以及每个线程被分配到处理器的时间片长度。
时间分片:
一种调度策略,用于在多任务操作系统中切换线程的执行。
在时间分片调度中,处理器被划分为若干个固定长度的时间片(或时间量子),每个时间片用于给一个线程执行一段时间。
当时间片用完后,线程调度器会中断当前线程的执行,并将处理器分配给下一个等待执行的线程。
33、同步方法和同步块,哪个是更好的选择?
同步方法:
使用同步方法时,整个方法被标记为同步,只有一个线程可以执行该方法,其他线程必须等待。
Java回自动使用当前对象作为锁。同步方法适用于对于整个方法体都需要进行同步的情况,
如果只有方法中的某一部分需要同步,同步方法就不太合适。
同步代码块:
使用synchronized关键字,指定锁对象实现。获取到锁对象的线程才能执行,同步块适用于只有一部分代码需要同步的情况。
同步块是更好的选择,因为它不会锁住整个对象。同步方法会锁住整个对象。
34、 线程池中提交一个任务的流程是怎样的?
- 1、任务提交:将任务提交给线程池。
- 2、任务接收:线程池接收任务,检查线程池状态,是否已满或者关闭。如果线程池已满,任务可能会被拒绝或者进入等待状态。
- 3、任务调度:线程池选择一个空闲的线程执行任务。
- 4、任务执行:选定的工作线程执行任务代码,如果是Runnable任务,执行run()方法,如果是Callable任务,执行call()方法,返回一个结果。
- 5、任务完成:将结果返回给任务提交者或者进行其他任务。
- 6、线程服用:任务完成后,线程不会销毁,而是继续留在线程池等待下一个任务的到来。
35、如何优雅的停掉一个线程?
-
使用标志变量:
定义一个标志变量控制线程的执行状态,在线程执行逻辑中, 通过检查标志变量的状态判断是否已停止。
// 在线程类中定义一个标志变量
public class MyThread extends Thread {
private volatile boolean stopped = false;
public void run() {
while (!stopped) {
// 执行任务
}
}
public void stopThread() {
stopped = true;
}
}
// 在外部代码中停止线程
MyThread myThread = new MyThread();
myThread.start();
// 停止线程
myThread.stopThread();
-
使用interrupt()方法:
调用线程的 interrupt() 方法可以发送一个中断信号给线程。 在线程的执行逻辑中, 通过检查线程的中断状态(使用 isInterrupted() 方法)来判断是否停止线程。
// 在线程类的执行逻辑中检查中断状态
public class MyThread extends Thread {
public void run() {
while (!isInterrupted()) {
// 执行任务
}
}
}
// 在外部代码中停止线程
MyThread myThread = new MyThread();
myThread.start();
// 停止线程
myThread.interrupt();
-
使用 Thread.interrupted() 方法
Thread 类的静态方法 interrupted() 可以检查当前线程的中断状态, 并清除中断状态。
// 在线程类的执行逻辑中检查中断状态
public class MyThread extends Thread {
public void run() {
while (!Thread.interrupted()) {
// 执行任务
}
}
}
// 在外部代码中停止线程
MyThread myThread = new MyThread();
myThread.start();
// 停止线程
myThread.interrupt();
36、线程池分为哪几种,分别如何创建?
加粗样式
-
FixedThreadPool 固定线程池
包含固定数量,一旦创建就一直是这么多,即时有线程空闲,也不会回收。 如果任务超过线程池数量,则等待,直到有可用。
ExecutorService executor = Executors.newFixedThreadPool(5);
-
CachedThreadPool缓存线程池
根据需要创建新的线程。 如果有线程可用则重用线程,如果没有创建新线程。 线程在空闲一段时间后会被回收i,适用于执行时间较短的任务。
ExecutorService executor = Executors.newCachedThreadPool();
-
SingleThreadExecutor单线程线程池
只包含一个线程,用于按顺序执行任务。 如果该线程异常退出,将创建一个新的替代。
ExecutorService executor = Executors.newSingleThreadExecutor();
-
ScheduledThreadPool定时任务线程池
用于执行定时任务和周期性任务。 可根据需要创建多个线程,在执行延迟时间或固定周期内执行任务。
ScheduledExecutorService executor = Executors.newScheduledThreadPool(3);
37、什么是CAS?
一种并发编程中的原子操作。用于实现多线程环境下无锁同步。
cas 是一种基于锁的操作,而且是乐观锁。
CAS操作包括三个参数:内存位置,预期值,新值。
CAS 操作会先比较内存位置的值与预期值是否相等,如果相等,则将新值更新到内存位置;如果不相等,则不进行任何操作。
38、CAS和锁的区别是什么?
cas是无锁机制,不会引起线程阻塞。
锁机制需要线程进入临界区,如果锁被其他线程占用,则需要等待,可能引起线程的阻塞。
39、CAS在什么场景常用?
CAS在以下场景下特别有用:
线程安全的计数器:当多个线程同时对一个计数器进行增加或减少操作时,CAS可以确保线程安全,避免数据竞争和不一致的结果。
非阻塞算法:CAS可以用于实现非阻塞算法,其中线程在执行过程中不会被挂起,而是通过CAS操作来保持数据的一致性。
无锁数据结构:CAS可以用于实现无锁的数据结构,如无锁队列、无锁堆栈等,避免了使用传统锁带来的开销和竞争。
CAS使用示例:
package com.wb.demo;
import java.util.concurrent.atomic.AtomicInteger;
public class CASExample {
// 多个线程并发地通过CAS操作增加计数器的值。
private static AtomicInteger counter = new AtomicInteger(0);
public static void main(String[] args) {
// 启动多个线程并发增加计数器的值
for (int i = 0; i < 10; i++) {
new Thread(() -> {
int oldValue, newValue;
do {
oldValue = counter.get(); // 获取当前值
newValue = oldValue + 1; // 计算新值
//每个线程通过compareAndSet方法来比较当前值是否与期望值相等,如果相等,则更新为新值。
//如果更新失败,则继续循环尝试,直到更新成功。
} while (!counter.compareAndSet(oldValue, newValue)); // CAS操作尝试更新值
}).start();
}
try {
Thread.sleep(1000); // 等待所有线程执行完成
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Final value: " + counter.get()); // 输出最终结果
}
}
40、 ThreadLocal是什么,有什么作用?
ThreadLocal是多线程下存储线程局部变量的工具类。为每个线程提供了独立的变量副本,每个线程都可以独立改变自己的副本,不影响其他线程。
ThreadLocal的主要作用是在多线程的场景下提供线程封闭性,即使多个线程访问同一个ThreadLocal对象,它们各自持有的变量副本是相互隔离的。这对于一些需要在线程间隔离数据的情况非常有用,它可以避免线程间的数据共享和同步带来的并发问题。
主要场景:
Ⅰ、保持上下文信息:(用户身份,请求信息)在整个应用的不同层级或模块间进行传递,
可以使用ThreadLocal将这些上下文信息存储在线程中,不同模块可以方便地获取并使用。
Ⅱ、事务管理:将事务对象存储在ThreadLocal中,使得同一个线程中的多个方法可以共享同一个事务对象,
而不需要显式地传递。
Ⅲ、线程安全的日期格式化: 在Java中,日期格式化类通常是非线程安全的,
如果多个线程同时使用同一个日期格式化对象,会导致错误的结果。
使用ThreadLocal可以为每个线程提供一个独立的日期格式化对象,确保线程安全。