多线程-面试题

多线程

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可以为每个线程提供一个独立的日期格式化对象,确保线程安全。
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值