多线程-面试题

多线程面试题汇总 使用其他语言写的python解析器(不推荐,还是用python官方的CPython好)不使用多线程,使用多进程-进程里面加协程实现多任务来充分利用多核CPU(推荐)即使存在GIL,在有IO等待操作的程序中,还是多线程快;当然没有资源等待的还是使用单线程快(科学计算、累加等等)但是需要注意的是线程有了GIL后并不意味着使用python多线程时不需要考虑线程安全,GIL的存在是为了方便使用C语言CPython解释器的编写者,而顶层使用python时依旧要考虑线程安全。 阅读详情

多线程

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();
            }
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xb6DJIIa-1685017029574)(C:\Users\ROG\AppData\Roaming\Typora\typora-user-images\image-20230525195623196.png)]

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可以为每个线程提供一个独立的日期格式化对象,确保线程安全。
38道多线程核心面试题(附答案) 今天给大家分享的是比较全面的多线程面试题,大家在面试的过程中不免会被问到很多专业性的问题,有的时候回答的并不是那么全面和精细,这仅仅代表个人观点。 阅读详情

相关推荐

面试题汇总二 Java 多线程篇

前言 题目汇总来源史上最全各类面试题汇总,没有之一,不接受反驳 面试题汇总一 Java 语言基础篇 面试题汇总二 Java 多线程篇 面试题汇总三 Java 集合篇 面试题汇总四 JVM 篇 面试题汇总五 Spring 篇 面试题汇总六 数据库篇 面试题汇总七 计算机网络篇 目录 前言 多线程 开启线程的三种方式? 什么是Callable和Future? 说说进程...

u013575592的博客 1480

【多线程与高并发】这可能是最全的多线程面试题了

1. 如何预防死锁? 首先需要将死锁发生的是个必要条件讲出来: 互斥条件 同一时间只能有一个线程获取资源。 不可剥夺条件 一个线程已经占有的资源,在释放之前不会被其它线程抢占 请求和保持条件 线程等待过程中不会释放已占有的资源 循环等待条件 多个线程互相等待对方释放资源 死锁预防,那么就是需要破坏这四个必要条件: 由于资源互斥是资源使用的固有特性,无法改变,我们不讨论 破坏不可剥夺条件 一个进程不能获得所需要的全部资源时便处于等待状态,等待期间他占有的资源将被隐式的释放重新加入到系统的资源列表中

牧小农 1万+

多线程笔试面试题汇总

多线程在笔试面试中经常出现,下面列出一些公司的多线程笔试面试题。首先是一些概念性的问答题,这些是多线程的基础知识,经常出现在面试中的第一轮面试。然后是一些选择题,这些一般在笔试时出现,虽然不是太难,但如果在选择题上花费大多时间无疑会对后面的编程题造成影响,因此必须迅速的解决掉。最后是综合题即难一些的问答题或是编程题。这种题目当然是最难解决了,要么会引来面试官的追问,要么就很容易考虑不周全,因此解决

CoolBoy 3689

面试题:20道多线程面试题与答案

答案:ThreadLocal是Java中的一个线程局部变量,它可以保证每个线程都有自己的变量副本,线程之间互不干扰。当一个线程获取到数据后,会记录数据的版本号或时间戳,然后在更新数据时会检查数据的版本号或时间戳是否发生变化,如果没有变化则可以更新数据,否则说明数据已经被其他线程修改过,更新失败。答案:线程池是一种线程管理机制,它可以重用已创建的线程,避免频繁创建和销毁线程带来的开销。答案:线程池的拒绝策略是指当线程池中的线程数量达到上限,并且任务队列也满了时,新提交的任务该如何处理的策略。

good7ob的博客 4992

java多线程面试题及答案,得物java面试题

前言 原来只是在从应用以及简单的思想理解方面给大家介绍了红黑树的创建与使用。这段时间就想要深入研究一下红黑树,主要参考了维基百科内容,加上自己的一些理解与学习,尝试真正的实现这一数据结构。今天小泉就带大家手撸一下红黑树吧!废话不多说,我们的红黑树之旅即将启程。 异步复制 MySQL的复制默认是异步的,主从复制至少需要两个MYSQL服务,这些MySQL服务可以分布在不同的服务器上,也可以在同一台服务器上。 MySQL主从异步复制是最常见的复制场景。数据的完整性依赖于主库BINLOG的不丢失,只要主库的BINL

m0_68749532的博客 1076

Java多线程面试题59题详解集合

死锁的发生必须满足以下四个条件,它们是:互斥条件:资源不能被共享,只能由一个线程使用。请求与保持条件:一个线程因请求被占用的资源而阻塞时,对已获得的资源保持不放。不剥夺条件:线程已获得的资源,在未使用完之前,不能被其他线程强行夺走,只能由占有资源的线程主动释放。循环等待条件:存在一种线程资源的循环等待关系。为了更好地理解死锁,我们来模拟一个简单的死锁场景:try {});try {});

weixin_42596214的博客 775

2023 Java多线程面试高频考点深度解析:从线程池到锁优化的指南

"本文系统梳理Java多线程核心面试考点,涵盖线程状态转换、synchronized锁升级、AQS底层原理、线程池工作机制及ConcurrentHashMap优化等高频难点。通过20+代码示例详解CAS原子操作、读写锁应用、线程安全实现等关键技术,助开发者深入理解多线程并发编程精髓,从容应对大厂技术面试。适合中高级Java开发者构建完整多线程知识体系。"

Zyw907155124的博客 2254

Appstore 3.2(f)违规自救指南:从触发到申诉的完整应对策略

本文详细解析了Appstore 3.2(f)违规的应对策略,从触发原因到申诉流程,帮助开发者有效自救。文章涵盖隐瞒行为、账户关联和信息造假三大违规场景的判定逻辑,并提供申诉信写作的黄金法则和长期合规防御措施,确保开发者能够顺利通过苹果审核。

weixin_29304985的博客 662

【面试题系列】Java 多线程面试题深度解析

本文涉及Java 多线程面试题,从基础到高级,希望对你有所帮助!

颜淡慕潇 5771

COMSOL多物理场仿真:瓷绝缘子电热耦合建模与优化策略

本文详细介绍了使用COMSOL Multiphysics进行瓷绝缘子电热耦合仿真的完整流程与优化策略。文章阐述了电热耦合效应的工程意义,并逐步讲解了从几何建模、材料定义、物理场设置到边界条件与网格划分的关键步骤。重点分析了如何通过焦耳热耦合实现电场与温度场的双向交互,以及如何利用仿真结果进行参数化优化和瞬态故障模拟,为提升电力设备可靠性提供有效的数字化设计工具。

n8m7b6v5c4的博客 884

(R,线性回归)R语言里的模型诊断图(Residuals vs Fitted,Normal QQ , Scale-Location ,Residuals Leverage)

线性回归,是概率统计学里最重要的统计方法,也是机器学习中一类非常重要的算法。线性模型简单理解非常容易,但是内涵是非常深奥的。尤其是线性回归模型中的Diagnostics plot的阅读与理解一直被认为是线性回归中的一个难点。 在任何线性模型中,能够直接“lm”(模型有意义),既要考虑各个参数的t-test所得出的p-value,也要考虑总体模型F-检验得出的p-value。在这之后,还要清楚一个...

qq_35837578的博客 7万+

中级:多线程面试题必知必会

多线程是Java编程中构建高性能应用程序的关键技术,面试中对多线程的考察主要集中在多线程基础、线程安全、锁机制、线程池以及并发工具类等方面。通过本文的学习,读者可以深入理解这些知识点,并通过代码示例掌握其实际应用。在实际开发中,合理运用多线程技术可以提高程序的性能和响应速度。

佩奇的技术笔记 940

关于SVPWM七段式五段式调制后的相电压和线电压波形——大疆DJI技术笔试

1.SVPWM七段式/五段式调制的相电压和线电压波形 脉宽调制(PWM)在并网变流技术中得到了广泛的应用。与正弦脉宽调制(SPWM)的控制方法相比,SVPWM方法具有直流侧电压利用率高,电压谐波小,易于数字化实现等显著的优点,从而使SVPWM在实际的应用中越来越普遍。TMS320F28335是32位浮点的DSP控制器,具有150MHz的高速处理能力,18路PWM输出,与前几代数字信号控制器相比,性能平均提高了50%。其中具体的SVPWM理论实现的原理,请参考我的博客永磁同步电机的矢量控制策略(七)一一一SV

昔时扬尘处 2万+

多线程高频基础面试题

一. 线程与进程的区别 二. 并发与并行的区别 三. 创建线程的方式有哪些 四. Runnable和Callable有什么区别 五. 线程包括哪些状态,状态之间是如何变化的 六. 在Java中wait和sleep方法的不同 七. 新建三个线程,如何保证它们按顺序执行 八. notify()和notifyAll()有什么区别 九. 线程的run()和start()有什么区别 十. 如何停止一个正在运行的线程

几许的博客 1261

抖音X-Medusa风控逆向:unidbg环境重建与魔改AES还原

X-Medusa并非传统加密算法,而是深度耦合运行时环境的动态混淆系统,其核心是硬件指纹感知、线程上下文绑定与非标AES变异。理解其原理需跳出‘找密钥’思维,转向对CPU型号、系统属性、微秒级时间及内存布局的综合建模。技术价值在于将不可控的客户端风控,转化为可复现、可配置、可运维的服务化能力;典型应用场景包括合规数据采集、API协议适配与移动安全研究。本文聚焦Android native层逆向实践,基于unidbg实现高保真环境模拟,并完整还原S-Box动态生成、MixColumns矩阵变异与轮密钥XOR扰

weixin_33783283的博客 686

STM32F103C8T6全桥逆变电路PWM波生成(基于HAL库)

全桥逆变电路需要两组开关管相互协同工作,故需要两路PWM控制信号,为了防止电源短路,需要保证同一桥臂的上下两个开关管不会同时导通,故两路PWM在相位上需要相差180°;为了保证高频变压器不会磁饱和,使正负半周PWM占空比相同。但是传统的互补输出达不到调节占空比调节输出电压的效果。......

weixin_45042735的博客 6025

秒杀多线程第一篇 多线程笔试面试题汇总

系列前言    本系列是本人参加微软亚洲研究院,腾讯研究院,迅雷面试时整理的,另外也加入一些其它IT公司如百度,阿里巴巴的笔试面试题目,因此具有很强的针对性。系列中不但会详细讲解多线程同步互斥的各种“招式”,而且会进一步的讲解多线程同步互斥的“内功心法”。有了“招式”和“内功心法”,相信你也能对多线程挥洒自如,在笔试面试中顺利的秒杀多线程试题。              -------------

MoreWindows Blog 21万+

高通Camx功能feature分析之一:remosaic功能导通

本文详细介绍了S5K3P9S传感器前置场景中SW Remosaic功能的实现流程。首先需在sensorxml中确认支持SW Remosaic类型,并集成remosaic相关库文件(libremosaic_wrapper.so等)。核心实现包括:初始化阶段调用InitFrontSamsungRemosaic();执行线程DeferFrontInitThread()读取校准数据;通过DoSamsungRemosaic()执行remosaic算法处理;最后在析构函数中调用DeinitFrontSamsungRem

1700

c++中多线程面试题

进程内的一个执行流,拥有自己的栈、寄存器上下文,但与同属进程的其他线程共享地址空间、文件句柄等资源。

JC的博客 1577

【unity实战】完美的2D横版平台跳跃玩家控制器,使用InputSystem+有限状态机实现人物加速减速 多段跳 长短跳 多段攻击 冲锋 冲锋残影 滑墙 蹬墙跳 滑墙 土狼时间 预输入 攀爬 爬楼梯

【unity实战】使用unity的新输入系统InputSystem+有限状态机设计一个玩家状态机控制——实现玩家的待机 移动 闪避 连击 受击 死亡状态切换这次来实现一个2d平台平台跳跃玩家控制器,平台人物控制要做的好,要考虑的问题会有很多,比如移动加速减速 多段跳 冲锋 冲锋残影 滑墙 蹬墙跳 土狼时间 预输入 攀爬 爬楼梯//减速至于为什么叫这个名字(土狼时间)呢?其实这个名词来自于华纳兄弟动画公司,一部古老的动画系列影片《Looney Tunes》

向宇的博客,专注php/web全栈 unity游戏开发,欢迎大家评论纠错 6044

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

多线程java_Java 多线程面试题的总结

阅读本文约需要10分钟大家好,我是你们的导师,我每天都会在这里给大家分享一些干货内容(当然了,周末也要允许老师休息一下哈)。上次老师跟大家分享了下微服务架构的常用设计模式的相关知识,今天跟大家分享Java 多线程面试题的总结的知识。1Java 多线程面试题这些多线程的问题,有些来源于各大网站、有些来源于自己的思考。可能有些问题网上有、可能有些问题对应的答案也有、也可能有些各位网...

weixin_39635459的博客 154

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

java多线程面试题

原文链接:http://www.importnew.com/12773.html         本文由 ImportNew - 李 广 翻译自 javarevisited。欢迎加入Java小组。转载请参见文章末尾的要求。   不管你是新程序员还是老手,你一定在面试中遇到过有关线程的问题。Java语言一个重要的特点就是内置了对并发的支持,让Java大受企业和程序员的欢迎。大多数待遇丰

super_ozman的专栏 663

Devexpress16.2.6破解工具

Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具Devexpress16.2.6破解工具

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

秒杀多线程第一篇 多线程笔试面试题汇总 ZZ 【多线程】

http://blog.youkuaiyun.com/morewindows/article/details/7392749 系列前言 本系列是本人参加微软亚洲研究院,腾讯研究院,迅雷面试时整理的,另外也加入一些其它IT公司如百度,阿里巴巴的笔试面试题目,因此具有很强的针对性。系列中不但会详细讲解多线程同步互斥的各种“招式”,而且会进一步的讲解多线程同步互斥的“内功心法”。有了“招式”...

dongkai0918的博客 172

谈谈这几个常见的多线程面试题

谈谈这几个常见的多线程面试题 转载于微信面试题精选 点我查看原文

weixin_44283127的博客 134

java多线程常见面试题(上)

前言 Java 语言一个重要的特点就是内置了对并发的支持,让 Java 大受企业和程序员的欢迎。大多数待遇丰厚的 Java 开发职位都要求开发者精通多线程技术并且有丰富的 Java 程序开发、调试、优化经验,所以线程相关的问题在面试中经常会被提到。 在典型的 Java 面试中, 面试官会从线程的基本概念问起, 如:为什么你需要使用线程, 如何创建线程,用什么方式创建线程比较好(比如:继承 thre...

测试架构师养成记的博客 1912

50个多线程面试题

50个多线程面试题链接

ShyTan的博客 180
上一篇: Redis-面试题
下一篇: JVM学习笔记
wáng bēn
博客等级 码龄7年 97粉丝 56原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wáng bēn

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值