Java高频面试之并发篇

有需要互关的小伙伴,关注一下,有关必回关,争取今年认证早日拿到博客专家

Java高频面试之总纲篇

Java高频面试之集合篇

Java高频面试之异常篇

Java高频面试之并发篇

Java高频面试之SSM篇

Java高频面试之Mysql篇

Java高频面试之Redis篇

Java高频面试之消息队列与分布式篇

50道SQL面试题

奇奇怪怪的面试题

五花八门的内存溢出

并行和并发有什么区别?

并行是同时执行多个任务,而并发是多个任务在一段时间内交替执行。

并行(Parallel)是指同时执行多个任务或操作,通过同时利用多个计算资源来提高系统的处理能力。在并行处理中,任务被划分为多个子任务,并且这些子任务可以同时执行,每个子任务分配给不同的处理单元(如多核处理器或分布式系统中的多个计算节点)。通过并行执行,可以加快任务的完成速度,提高系统的吞吐量。

并发(Concurrent)是指多个任务在一段时间内交替执行。这并不意味着同时执行多个任务,而是任务在时间上重叠执行。在并发处理中,系统可能只有一个处理单元,但任务通过时间片轮转或其他调度算法进行切换,以实现多个任务的看似同时执行。并发可以提高系统的响应能力和资源利用率,特别是在涉及输入/输出等等待时间的情况下。
总结
并行:同时执行多个任务,通过利用多个计算资源提高系统处理能力。
并发:多个任务在一段时间内交替执行,提高系统的响应能力和资源利用率。
并行可以在多个计算资源上同时执行多个任务,而并发是在一个计算资源上交替执行多个任务。

线程和进程的区别?

定义:进程是程序的执行实例,它具有独立的内存空间和系统资源。线程是进程中的一个执行单元,多个线程可以共享进程的内存空间和系统资源。
线程切换开销相对小,但需要进行同步操作;进程是具有独立地址空间和资源的执行实例,切换开销大,但数据隔离性好。

守护线程是什么?

守护线程(Daemon /ˈdiːmən/ Thread)是在程序运行过程中在后台提供服务的线程。当所有的非守护线程结束时,守护线程也会随之自动结束,无论它是否执行完任务。
守护线程在后台默默地运行,为其他线程提供支持和服务,如垃圾回收、监控、自动保存等
与非守护线程相比,守护线程有以下特点:
生命周期:守护线程的生命周期与程序的生命周期相同,即当所有的非守护线程结束时,守护线程也会被终止。
任务执行:守护线程通常用于执行一些支持性任务,不负责执行核心业务逻辑。
程序退出:如果只剩下守护线程在运行,程序会自动退出而不等待守护线程执行完任务。

Thread daemonThread = new Thread(new Runnable() {
    public void run() {
        // 守护线程的任务逻辑
    }
});
daemonThread.setDaemon(true); // 设置为守护线程
daemonThread.start(); // 启动线程

创建线程的几种方式?

  1. 继承Thread类重写run方法(无参构造方法)
class MyThread extends Thread {
    public void run() {
        // 线程的执行逻辑
    }
}

// 创建并启动线程
MyThread myThread = new MyThread();
myThread.start();

  1. 创建Thread,传入一个Runnable对象(有参构造)

    class MyRunnable implements Runnable {
        public void run() {
            // 线程的执行逻辑
        }
    }
    
    // 创建并启动线程
    MyRunnable myRunnable = new MyRunnable();
    Thread thread = new Thread(myRunnable);
    thread.start();
    

创建Runnable的方法

  1. 实现Runnable接口,重写run方法,并将其作为参数传递给Thread的构造方法
class MyRunnable implements Runnable {
    public void run() {
        // 线程的执行逻辑
    }
}
  1. 使用匿名内部类
Thread thread = new Thread(new Runnable() {
public void run() {
// 线程的执行逻辑
}
});
thread.start();
  1. 使用lambda表达式
Thread thread = new Thread(() -# {
    // 线程的执行逻辑
});
thread.start();

通常情况下,推荐使用实现Runnable接口或使用Lambda表达式的方式,因为它们能更好地支持代码的组织和重用,同时避免了单继承的限制

Runnable 和 Callable 有什么区别?

Runnable和Callable接口都用于创建可并发执行的任务,但它们有以下区别:

  1. 返回值:Runnable接口的run()方法没有返回值,它表示一个没有返回结果的任务。而Callable接口的call()方法可以返回任务的执行结果,它是一个带有泛型参数的接口,用于定义具有返回值的任务。

  2. 异常处理:Runnable接口的run()方法不能抛出检查异常,只能捕获并处理。而Callable接口的call()方法可以抛出检查异常,调用者需要显式处理这些异常或将其向上抛出。

    @FunctionalInterface
    public interface Runnable {
        /**
         * When an object implementing interface <code>Runnable</code> is used
         * to create a thread, starting the thread causes the object's
         * <code>run</code> method to be called in that separately executing
         * thread.
         * <p>
         * The general contract of the method <code>run</code> is that it may
         * take any action whatsoever.
         *
         * @see     java.lang.Thread#run()
         */
        public abstract void run();
    }
    
    @FunctionalInterface
    public interface Callable<V> {
        /**
         * Computes a result, or throws an exception if unable to do so.
         *
         * @return computed result
         * @throws Exception if unable to compute a result
         */
        V call() throws Exception;
    }
    
  3. 使用方式:Runnable接口通常作为线程的执行体,将任务逻辑放在run()方法中实现。Callable接口通常与ExecutorService一起使用,通过submit()方法提交Callable任务给线程池执行,并通过返回的Future对象获取任务的执行结果。

  4. 返回结果的获取:Runnable任务执行完毕后,无法直接获取任务的执行结果。而Callable任务执行完毕后,可以通过Future对象的get()方法获取任务的执行结果,该方法会阻塞调用线程直到任务执行完毕并返回结果。

// 使用Runnable接口创建任务
Runnable myRunnable = new Runnable() {
    public void run() {
        // 任务逻辑
    }
};

// 使用Callable接口创建任务
Callable<Integer> myCallable = new Callable<Integer>() {
    public Integer call() throws Exception {
        // 任务逻辑
        return 42; // 返回结果
    }
};

在Java中,通常使用ExecutorService来执行Runnable和Callable任务。ExecutorService提供了submit()方法用于提交任务,并返回代表任务执行结果的Future对象。使用Future对象可以判断任务是否完成,获取任务的执行结果,或取消任务的执行。

线程状态及转换?

操作系统中线程的5种状态

image.png

java中线程的6种状态

public enum State {
    /**
     * Thread state for a thread which has not yet started.
     */
    NEW,

    /**
     * Thread state for a runnable thread.  A thread in the runnable
     * state is executing in the Java virtual machine but it may
     * be waiting for other resources from the operating system
     * such as processor.
     */
    RUNNABLE,

    /**
     * Thread state for a thread blocked waiting for a monitor lock.
     * A thread in the blocked state is waiting for a monitor lock
     * to enter a synchronized block/method or
     * reenter a synchronized block/method after calling
     * {@link Object#wait() Object.wait}.
     */
    BLOCKED,

    /**
     * Thread state for a waiting thread.
     * A thread is in the waiting state due to calling one of the
     * following methods:
     * <ul>
     *   <li>{@link Object#wait() Object.wait} with no timeout</li>
     *   <li>{@link #join() Thread.join} with no timeout</li>
     *   <li>{@link LockSupport#park() LockSupport.park}</li>
     * </ul>
     *
     * <p>A thread in the waiting state is waiting for another thread to
     * perform a particular action.
     *
     * For example, a thread that has called <tt>Object.wait()</tt>
     * on an object is waiting for another thread to call
     * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
     * that object. A thread that has called <tt>Thread.join()</tt>
     * is waiting for a specified thread to terminate.
     */
    WAITING,

    /**
     * Thread state for a waiting thread with a specified waiting time.
     * A thread is in the timed waiting state due to calling one of
     * the following methods with a specified positive waiting time:
     * <ul>
     *   <li>{@link #sleep Thread.sleep}</li>
     *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
     *   <li>{@link #join(long) Thread.join} with timeout</li>
     *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
     *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
     * </ul>
     */
    TIMED_WAITING,

    /**
     * /ˈtɜːmɪneɪtɪd/
     * Thread state for a terminated thread.
     * The thread has completed execution.
     */
    TERMINATED;
}

image.png

https://blog.csdn.net/acc__essing/article/details/127470780?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522168404937816782425125388%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=168404937816782425125388&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-4-127470780-null-null.142^v87^control_2,239^v2^insert_chatgpt&utm_term=java%20%E7%BA%BF%E7%A8%8B%E7%8A%B6%E6%80%81%E4%BB%A5%E5%8F%8A%E5%88%87%E6%8D%A2&spm=1018.2226.3001.4187
https://my.oschina.net/goldenshaw?tab=newest&catalogId=3277710

sleep() 和 wait() 的区别?

  1. sleep() 用于暂停当前线程一段时间,不涉及锁或线程之间的协作(无任何同步语义,让出cpu,但不释放锁)。

    wait() 用于线程之间的协作,调用该方法后,线程会释放对象锁并进入等待状态,直到其他线程唤醒它(让出cpu,释放锁)。

  2. sleep() 是线程的静态方法,可以直接调用;而 wait() 是对象的方法,必须在同步代码块或同步方法中使用,并且需要配合 notify() 或 notifyAll() 方法使用。

线程的 run() 和 start() 有什么区别?

run() 方法只是在当前线程中同步执行线程代码(run方法的逻辑),而 start() 方法会创建一个新线程,并在新线程中异步执行线程代码。通常情况下,我们使用 start() 方法来启动线程,以便实现多线程并发执行的效果。

简单说就是一个在当前线程中执行run(),一个是新建一个线程执行run()

在 Java 程序中怎么保证多线程的运行安全?

  1. 同步代码块(Synchronized Blocks)
synchronized (sharedObject) {
    // 需要同步的代码块
}
  1. 同步方法(Synchronized Methods)
public synchronized void myMethod() {
    // 需要同步的代码
}
  1. 锁(Lock)
Lock lock = new ReentrantLock();

lock.lock(); // 获取锁
try {
    // 需要同步的代码
} finally {
    lock.unlock(); // 释放锁
}
  1. 原子类(Atomic Classes
AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet(); // 原子递增操作
  1. 使用线程安全的数据结构:在多线程环境下,使用线程安全的数据结构(如 ConcurrentHashMap、CopyOnWriteArrayList 等)来存储和操作共享数据,以确保线程安全。

Java 线程同步的几种方法?

  1. synchronized 关键字:使用 synchronized 关键字可以实现对代码块或方法的同步。当线程进入 synchronized 代码块或方法时,会自动获取对象的锁,并在执行完成后释放锁,确保同一时间只有一个线程可以执行该代码块或方法。
  2. Lock 类:ReentrantLock 是 java.util.concurrent.locks 包中提供的锁实现类,它提供了显式锁定和解锁的功能。使用 ReentrantLock 可以在代码中显式地指定锁定和解锁的位置,从而实现对共享资源的同步控制。
  3. LockSupport
  4. 线程安全的类:AtomicInteger /ConcurrentHashMap。
  5. volatile 关键字:使用 volatile 关键字可以标记变量为“可见性变量”,确保不同线程对该变量的修改在内存中可见。虽然 volatile 关键字不能解决复合操作的原子性问题,但它在某些场景下可以用于简单的线程同步。

Thread.interrupt() 方法的工作原理是什么?

Thread.interrupt() 向目标线程发送中断信号(即将目标线程的中断状态设置为 true,仅此而已)。Thread.interrupt() 方法并不能直接终止目标线程的执行。它只是改变了目标线程的中断状态,需要目标线程自行检查中断状态并作出相应的响应(一个线程不应该由其他线程强行终止)。

具体工作原理如下:

如果目标线程处于阻塞状态(如调用了 Object.wait()、Thread.sleep()、BlockingQueue.take() 等方法),它将立即抛出 InterruptedException 异常(并且唤醒目标线程),并且中断状态会被清除(重置为 false)。

如果目标线程在执行过程中检查了中断状态(通过 Thread.interrupted() 或 Thread.isInterrupted() 方法),则中断状态为 true。线程可以根据中断状态采取适当的操作,例如终止线程的执行或进行清理工作。

需要注意的是,Thread.interrupt() 方法仅仅是改变了目标线程的中断状态,具体的中断响应逻辑由目标线程自行决定。通常情况下,目标线程在合适的时机检查中断状态并采取相应的处理措施,例如退出执行循环或释放资源。
总结而言,Thread.interrupt() 方法通过改变目标线程的中断状态来请求目标线程中断,但具体的中断响应逻辑由目标线程自行处理。

Thread thread = new Thread(() -> {
    System.out.println("线程被启动了");
    while (!Thread.interrupted()) {
        System.out.println(System.currentTimeMillis());
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        if (Thread.interrupted()) {
            System.out.println("线程被中断了");
            break;
        }
    }
});
thread.start();
// 确保thread已经进入等待状态
Thread.sleep(500);
thread.interrupt();
线程被启动了
1684204424982
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at Xxx.lambda$main$0(Xxx.java:10)
	at java.lang.Thread.run(Thread.java:748)
1684204425483
1684204426483
1684204427484
...

谈谈对 ThreadLocal 的理解?

Thread中有个成员变量threadLocals

ThreadLocal.ThreadLocalMap threadLocals = null;

ThreadLocalMap中有一个数组

private Entry[] table;

Entry继承自WeakReference<ThreadLocal<?>>,key为ThreadLocal,value为Object(也就是我们存放的值)

static class Entry extends WeakReference<ThreadLocal<?>> {
    /** The value associated with this ThreadLocal. */
    Object value;

    Entry(ThreadLocal<?> k, Object v) {
        super(k);
        value = v;
    }
}

ThreadLocal 是 Java 中的一个线程封闭机制,它允许将某个对象与当前线程关联起来,使得每个线程都拥有自己独立的对象副本。简单来说,ThreadLocal 提供了一种在多线程环境下,每个线程都可以独立访问自己的对象副本的机制。

ThreadLocal 的主要特点和应用如下:

  1. 线程隔离:ThreadLocal 可以实现线程间数据的隔离,每个线程都可以独立地操作自己的 ThreadLocal 实例,而不会受其他线程的影响。这对于一些需要线程独享的数据非常有用,如线程安全的 SimpleDateFormat、数据库连接等。

  2. 线程上下文传递:ThreadLocal 可以用于在方法之间传递数据,而不需要显式地传递参数。某个方法可以将数据存储在 ThreadLocal 中,然后其他方法可以直接从 ThreadLocal 中获取这些数据,避免了方法之间参数传递的麻烦。

  3. 线程状态保存:ThreadLocal 可以用于保存线程的状态信息。在多个方法调用之间,线程可以将状态信息存储在 ThreadLocal 中,以便后续方法可以方便地访问和使用。

在哪些场景下会使用到 ThreadLocal?

ThreadLocal 在以下场景下常被使用:

  1. 线程安全的对象:当一个对象是线程安全的,即可以被多个线程同时访问,但每个线程需要独立拥有自己的对象副本时,可以使用 ThreadLocal。典型的例子是线程安全的日期格式化器 SimpleDateFormat,每个线程可以拥有自己的日期格式化器副本,避免了多线程访问共享的 SimpleDateFormat 对象时可能出现的线程安全问题。

  2. 保存线程上下文信息:在多个方法调用之间需要传递一些上下文信息,但不希望在每个方法中显式传递参数时,可以使用 ThreadLocal。例如,在 Web 应用程序中,可以将当前用户信息存储在 ThreadLocal 中,这样在不同的方法中可以方便地获取用户信息,而不需要显式地传递用户参数。

  3. 数据库连接管理:在多线程环境中,为每个线程管理一个独立的数据库连接是常见的需求。通过使用 ThreadLocal,可以为每个线程维护一个独立的数据库连接副本,从而避免了线程间的数据库连接竞争和冲突。

  4. 事务管理:在一些需要实现线程级别的事务管理的场景下,可以使用 ThreadLocal 来存储当前线程的事务上下文。这样,每个线程都可以独立地管理自己的事务状态,而不会影响其他线程的事务。

  5. 日志跟踪:在日志系统中,通过 ThreadLocal 可以轻松地将每条日志的相关信息(如请求 ID、用户 ID)与当前线程关联起来,使得日志记录更加准确和可追踪。

总的来说,ThreadLocal 在需要实现线程隔离、线程上下文传递或线程状态保存的场景下非常有用。它提供了一种方便的机制,使得每个线程都可以独立地操作自己的对象副本,而不会受其他线程的干扰。

说一说对于 synchronized 关键字的了解?

synchronized 是 Java 中的关键字,用于实现线程的同步和互斥。它可以用于修饰方法、代码块和静态方法,用于控制对共享资源的访问。

synchronized 关键字的特性包括:

  • 互斥性:synchronized 保证了同一时间只有一个线程可以获得锁并执行同步代码块或方法,防止多线程并发访问共享资源导致的数据不一致性和冲突问题。
  • 可见性:synchronized 释放锁时会将修改的共享变量的值刷新到主内存,使得其他线程可以看到最新的值,保证了多线程间的数据一致性。
  • 内置锁:synchronized 使用的是内置锁(也称为监视器锁或互斥锁),每个对象都有一个与之关联的内置锁。当线程进入同步代码块时,它会尝试获取对象的内置锁,如果锁被其他线程持有,则线程进入阻塞状态,直到锁可用。

如何在项目中使用 synchronized 的?

同步代码块

同步方法

静态同步方法

说说 JDK1.6 之后的 synchronized 关键字底层做了哪些优化,可以详细介绍一下这些优化吗?

  1. 锁升级

    锁升级是指在多线程并发访问中,锁的状态从低级别的锁形式逐渐升级到高级别的锁形式,以提高并发性能和减少开销。下面介绍Java中的锁升级过程:

    1. 无锁状态(01):初始状态下,对象没有被线程持有锁,任何线程都可以访问。
    2. 偏向锁(101):当一个线程获得锁时,JVM会将锁标记置为偏向锁,并将线程ID记录在锁对象的对象头中。此时,其他线程可以继续访问该对象,无需进行同步操作(修改锁对象对象头中的线程id)。只有当线程竞争锁时(一个线程在同步代码外要进去,一个线程在同步代码里面),偏向锁会被撤销,锁状态升级为轻量级锁。
    3. 轻量级锁(00):当偏向锁撤销后,JVM将锁状态升级为轻量级锁。轻量级锁使用CAS(Compare and Swap)与自旋实现,当自旋一定次数后获取不到锁,锁状态将进一步升级。
    4. 重量级锁(10):当轻量级锁的CAS操作失败,JVM将锁状态升级为重量级锁。重量级锁使用操作系统提供的互斥量来实现线程之间的同步,线程在获取锁时会进入阻塞状态。重量级锁保证了多个线程之间的互斥访问,但会增加线程上下文切换的开销。

    image.png

  2. 锁粗化

    将多个连续的细粒度锁操作合并成一个更大的粗粒度锁操作,从而减少锁的获取和释放频率,提高性能.

    优化前代码

Object lock = new Object();
synchronized (lock){
    // 操作1
}
synchronized (lock){
    // 操作2
}
synchronized (lock){
    // 操作3
}

优化后代码

Object lock = new Object();
synchronized (lock){
    // 操作1
    // 操作2
    // 操作3
}
  1. 锁消除

    编译器级别消除不必要的锁操作,从而提高程序性能。

    优化前

public class LockEliminationExample {
    public void performOperation() {
        StringBuilder sb = new StringBuilder();

        // 不会被多线程共享的局部变量
        String localVariable = "Local";

        // 锁对象只在当前方法内使用
        synchronized (sb) {
            // 执行需要同步的操作
            sb.append("Hello");
            sb.append("World");
            sb.append(localVariable);
        }

        System.out.println(sb.toString());
    }
}

优化后

public class LockEliminationExample {
    public void performOperation() {
        StringBuilder sb = new StringBuilder();

        // 不会被多线程共享的局部变量
        String localVariable = "Local";

        // 执行需要同步的操作,锁消除
        sb.append("Hello");
        sb.append("World");
        sb.append(localVariable);

        System.out.println(sb.toString());
    }
}

谈谈 synchronized 和 ReenTrantLock 的区别?

synchronized和ReentrantLock都是Java中用于实现线程同步的机制,它们有以下区别:

  1. 锁的获取方式:synchronized是Java语言层面的关键字,它在获取锁时会隐式地获取和释放锁。而ReentrantLock是Java提供的一个类,需要显式地调用lock()方法获取锁,以及调用unlock()方法释放锁。

  2. 可中断性:ReentrantLock提供了一种可中断的获取锁的方式。在获取锁时,线程可以选择等待一段时间,并且可以通过调用lockInterruptibly()方法实现在等待期间响应中断。而synchronized关键字的锁获取过程是不可中断的,一旦获取不到锁,线程将一直处于阻塞状态,直到获取到锁或者抛出异常。

  3. 公平性:ReentrantLock可以是公平锁或非公平锁,而synchronized关键字默认情况下是非公平锁。公平锁会按照线程请求的顺序来获取锁,而非公平锁则允许插队获取锁。在高并发情况下,公平锁可能会导致线程饥饿,但可以避免线程的不公平竞争。

  4. 锁的可重入性:ReentrantLock和synchronized都支持锁的可重入性(也称为递归锁)。同一个线程可以多次获取同一个锁而不会发生死锁。在ReentrantLock中,线程必须显式地调用unlock()方法相同次数的释放锁。而synchronized关键字则会自动进行锁的释放。

  5. 扩展功能:ReentrantLock相对于synchronized提供了一些额外的功能,如可定时的、可轮询的、可中断的锁获取方式,以及可选择公平性。此外,ReentrantLock还支持多个条件变量(Condition),可以通过多个条件变量来控制线程的等待和唤醒。

需要注意的是,synchronized是Java语言内置的关键字,使用简单方便,适用于大多数的同步场景。而ReentrantLock是一个类,提供了更多灵活性和扩展功能,适用于需要更精细控制的同步场景。在选择使用synchronized还是ReentrantLock时,可以根据具体需求和场景来进行选择。

synchronized 和 volatile 的区别是什么?

synchronized和volatile是Java中用于实现线程安全的关键字,它们有以下区别:

  1. 作用范围:synchronized修饰方法或代码块。volatile用于修饰变量。
  2. 同步性质:synchronized保证可见性、原子性(这里的原子性指的是.同一时刻只能有一个线程操作,中间不能被其他线程操作,没有要么同时成功要么同时失败的语义)和一定程度的禁重排(synchronized内部的和外部的不能重排,但是内部是可以重排的,证据:双重检查为什么要加volatile修饰)。volatile只保证可见性和禁止指令重排(内存屏障),不保证原子性(多线程对volatile修饰的变量的i++操作).
  3. synchronized进入后会从主内存读,出来后会写会主内存,保证了可见性.volatile写立马刷会主内存,并让其他线程中对应的volatile修饰的变量失效,以此来保证内存的可见性.
  4. 应用场景:synchronized适用于多线程对共享资源进行并发访问和修改的情况,它提供了互斥访问的能力。volatile确保多个线程之间对变量的修改可见(没有复合操作可以用volatile),但不提供互斥访问的能力。

需要注意的是,synchronized提供了更强的线程安全性和数据一致性,但在使用时需要考虑锁的开销和可能的死锁情况。volatile关键字的使用更轻量,适用于简单的变量读写操作,但无法解决复合操作的原子性问题。在选择使用synchronized还是volatile时,需要根据具体的场景和需求来进行选择。

谈一下你对 volatile 关键字的理解?

当多个线程访问共享变量时,为了保证可见性和避免指令重排序带来的问题,可以使用volatile关键字来修饰变量。

volatile关键字具有以下特性:

  1. 可见性(Visibility):对于被volatile修饰的变量,在一个线程中对其进行修改后,其他线程能够立即看到最新的值。这是因为volatile关键字会告知编译器和处理器不要对该变量进行缓存,每次读取时都要从主内存中获取最新值。

  2. 禁止指令重排序(Ordering):volatile关键字会防止编译器和处理器对volatile变量的指令进行重排序。这意味着volatile变量的读写操作都会按照代码的顺序执行,不会被重新排序。

需要注意的是,volatile关键字仅适用于单个变量的读写操作,并不能保证复合操作的原子性。例如,volatile关键字无法保证i++这种操作的原子性,因为该操作包括读取、自增和写回三个步骤,而volatile关键字只能保证单个变量的读写操作的原子性。

另外,volatile关键字的使用相对于synchronized来说更轻量,因为它不需要获取锁来实现线程间的同步,但在某些情况下可能需要额外的控制机制来保证一致性。

总结起来,volatile关键字主要用于保证共享变量的可见性,确保对变量的修改能够及时被其他线程看到,并且禁止编译器和处理器对该变量进行重排序。它适用于对变量的简单读写操作,但不能解决复合操作的原子性问题。

说下对 ReentrantReadWriteLock 的理解?

import java.util.concurrent.locks.ReentrantReadWriteLock;

public class SharedResource {
    private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    private int value;

    public int getValue() {
        lock.readLock().lock();
        try {
            return value;
        } finally {
            lock.readLock().unlock();
        }
    }

    public void setValue(int newValue) {
        lock.writeLock().lock();
        try {
            value = newValue;
        } finally {
            lock.writeLock().unlock();
        }
    }
}

说下对悲观锁和乐观锁的理解?

  1. 悲观锁:假设容易发生冲突,因此会加锁操作,确保只有一个线程能够访问资源,其他线程需要等待。悲观锁适用于对数据修改频繁的场景。

  2. 乐观锁:假设不容易发生冲突,因此不需要加锁,而是在更新数据之前先进行验证,如果没有发现冲突,就进行更新,否则进行相应的冲突处理,例如重试或放弃更新。适用于读多写少的场景

乐观锁常见的两种实现方式是什么

乐观锁常见的两种实现方式是版本号(Versioning)和比较并交换(Compare and Swap,CAS)。

  1. 版本号(Versioning):
    版本号是乐观锁的一种实现方式。在这种方式中,每个数据项都会有一个与之关联的版本号(或时间戳)。当一个线程要更新数据时,它首先读取数据的当前版本号,并在更新之前将版本号加一。然后,它尝试将更新后的数据写回,并比较写回操作前后的版本号是否一致。如果一致,则表示没有其他线程修改了数据,更新成功;如果不一致,则表示发生了冲突,需要进行相应的冲突处理。

版本号的实现可以是一个整数字段,例如使用 Java 中的 AtomicInteger,或者是一个时间戳字段,记录数据的修改时间。这样,每次更新操作都会更新版本号,从而标识数据的变化。

  1. 比较并交换(Compare and Swap,CAS):
    比较并交换是另一种常见的乐观锁实现方式,它是一种原子操作。CAS 操作包括三个操作数:内存地址(或引用)、预期值和新值。它会比较内存地址处的值与预期值是否相等,如果相等,则将新值写入内存地址;否则,表示数据已被其他线程修改,CAS 操作失败。

CAS 操作可以通过硬件的原子指令来实现,是一种非阻塞的操作。在多线程环境下,多个线程可以同时尝试执行 CAS 操作来更新数据,但只有一个线程的 CAS 操作会成功,其他线程需要重试或进行相应的处理。

Java 提供了 java.util.concurrent.atomic 包下的原子类,例如 AtomicIntegerAtomicLong 等,它们使用 CAS 操作来实现线程安全的原子操作,可以作为乐观锁的一种实现方式。

这两种乐观锁的实现方式都是基于无锁的思想,避免了线程阻塞和上下文切换的开销,适用于读多写少的场景。选择哪种方式取决于具体的应用需求和环境。

乐观锁的缺点有哪些?

乐观锁虽然在某些场景下可以提供高性能和并发量,但也存在一些缺点:

  1. 冲突处理:乐观锁需要处理可能发生的冲突。如果在验证阶段发现数据已经被其他线程修改,当前线程必须进行相应的冲突处理,例如重试操作或放弃更新。这种处理过程可能会引入额外的开销,特别是在高并发环境下,如果冲突频繁发生,重试的开销可能会增加系统的负载。

  2. 无法保证一致性:乐观锁无法保证数据的强一致性。由于它在读取和更新之间不加锁,其他线程可能会修改数据,导致最终数据的不一致。乐观锁适用于那些可以容忍一定程度的数据不一致性的场景,例如并发读取的数据不要求绝对实时的一致性。

  3. 自旋开销:在乐观锁的实现中,当发生冲突时,通常会采用自旋的方式进行重试,即反复尝试更新数据,直到成功或达到一定的重试次数。自旋过程会占用 CPU 时间,可能导致额外的开销。如果冲突频繁发生,自旋次数过多会浪费大量的 CPU 资源。

  4. 难以应对长事务:乐观锁对于长事务的处理相对困难。在长时间的事务中,其他并发事务可能修改了事务操作的数据,导致更新失败。为了避免这种情况,需要进行额外的控制和冲突处理,增加了实现的复杂性。

综上所述,乐观锁的主要缺点是需要处理冲突、无法保证一致性、自旋开销和难以应对长事务。在选择使用乐观锁时,需要综合考虑应用场景、并发访问模式和数据一致性要求,权衡其优点和缺点。

CAS 和 synchronized 的使用场景?

cas适用于冲突少的场景

synchronized 适用于冲突多的场景

简单说下对 Java 中的原子类的理解?

Java中的原子类是一组线程安全的类,它们提供了原子操作的功能,可以在多线程环境下进行线程安全的操作。这些原子类位于java.util.concurrent.atomic包中,常见的原子类包括AtomicIntegerAtomicLongAtomicBoolean等。

原子类的主要特点如下:

  1. 原子操作:原子类提供了一系列的原子操作方法,这些方法在执行过程中不会被其他线程中断,保证了操作的原子性。
  2. 无锁的实现:原子类使用了底层的硬件支持或CAS(Compare and Swap)操作来实现无锁的线程安全操作。
  3. 原子类的操作是基于变量的:原子类主要针对基本数据类型(如整型、长整型、布尔型)进行原子操作。对于复杂的数据结构,需要使用原子类的组合操作或其他线程安全的机制来实现。
  4. 线程安全:原子类的操作都是线程安全的,多个线程可以并发地访问和修改原子变量,而不需要额外的同步控制。原子类使用了底层的原子操作指令或CAS操作来保证线程安全。

原子类在多线程编程中非常有用,特别适用于高并发环境下对共享变量进行原子操作的场景。它们提供了一种高效、简洁和线程安全的方式来操作共享变量,避免了使用传统的锁机制所带来的线程阻塞和上下文切换的开销。通过使用原子类,可以编写出更高效、可靠和可维护的多线程代码。

atomic 的原理是什么?

说下对同步器 AQS 的理解?

AQS 的原理是什么?

AQS 对资源的共享模式有哪些?

AQS 底层使用了模板方法模式,你能说出几个需要重写的方法吗?

java.util.concurrent.locks.AbstractQueuedSynchronizer#tryAcquire

说下对信号量 Semaphore 的理解?

CountDownLatch 和 CyclicBarrier 有什么区别?

说下对线程池的理解?为什么要使用线程池?

创建线程池的参数有哪些?

线程池的常见拒绝策略

  1. AbortPolicy拒绝执行,并抛出异常RejectedExecutionException.为默认拒绝策略
  2. CallerRunsPolicy让调用者自己执行
  3. DiscardPolicy丢弃
  4. DiscardOldestPolicy 丢弃最早的

如何创建线程池?

// 七大参数
int corePoolSize = 3;
int maximumPoolSize = 9;
long keepAliveTime = 30;
TimeUnit unit = TimeUnit.SECONDS;
BlockingQueue<Runnable# workQueue = new LinkedBlockingQueue<>(9);
ThreadFactory threadFactory = Executors.defaultThreadFactory();
RejectedExecutionHandler handler = new ThreadPoolExecutor.AbortPolicy();
ThreadPoolExecutor pool = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, threadFactory, handler);

线程池中的的线程数一般怎么设置?需要考虑哪些问题?

  1. 经验值

    cup密集型N+1

    IO密集型2N

  2. 计算公式

    最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 )* CPU数目

    很显然线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。

    虽说最佳线程数目算法更准确,但是线程等待时间和线程CPU时间不好测量,实际情况使用得比较少,一般用经验值就差不多了。再配合系统压测,基本可以确定最适合的线程数。

执行 execute() 方法和 submit() 方法的区别是什么呢?

  1. 返回值:execute()没有返回值,submit() 有返回值

  2. 异常处理:execute() 方法没有提供任何异常处理机制。如果提交的任务在执行过程中抛出异常,它将由内部的 UncaughtExceptionHandler 处理。而 submit()在获取结果的时候可以捕获到异常,然后自己处理。

    public static void main(String[] args) {
            ExecutorService pool = Executors.newFixedThreadPool(1);
            Runnable runnable = () -# {
                int i = 10 / 0;
            };
            pool.execute(runnable);
            pool.shutdown();
    
        }
    
    Exception in thread "pool-1-thread-1" java.lang.ArithmeticException: / by zero
    	at Xxx.lambda$main$0(Xxx.java:14)
    	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    	at java.lang.Thread.run(Thread.java:748)
    
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(1);
        Runnable runnable = () -# {
            int i = 10 / 0;
        };
        Future<?# submit = pool.submit(runnable);
        try {
            submit.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        pool.shutdown();
    }
    
    public static void main(String[] args) {
        ExecutorService pool = Executors.newFixedThreadPool(1);
        Runnable runnable = () -# {
            int i = 10 / 0;
        };
        Future<?# submit = pool.submit(runnable);
        try {
            submit.get();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
        pool.shutdown();
    }
    
    java.util.concurrent.ExecutionException: java.lang.ArithmeticException: / by zero
    	at java.util.concurrent.FutureTask.report(FutureTask.java:122)
    	at java.util.concurrent.FutureTask.get(FutureTask.java:192)
    	at Xxx.main(Xxx.java:14)
    Caused by: java.lang.ArithmeticException: / by zero
    	at Xxx.lambda$main$0(Xxx.java:10)
    	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
    	at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
    	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
    	at java.lang.Thread.run(Thread.java:748)
    
  3. 任务的包装:execute() 方法接受一个 Runnable 接口或其子类的任务对象作为参数。而 submit() 方法既可以接受 Runnable 接口的任务对象,也可以接受 Callable 接口的任务对象。

    java.util.concurrent.Executor#execute

    void execute(Runnable command);
    

    java.util.concurrent.ExecutorService#submit

    <T> Future<T> submit(Callable<T> task);
    Future<?> submit(Runnable task);
    
  4. Future 对象:submit() 方法返回一个 Future 对象,可以通过该对象来管理和获取任务的执行状态和结果。

execute() 方法用于提交不需要返回结果的任务,而 submit() 方法用于提交需要返回结果的任务,并提供更多的控制和异常处理机制。如果你关心任务的返回结果或需要更精细的异常处理,那么使用 submit() 方法会更加灵活和方便。如果你只需要提交简单的任务而不关心结果,execute() 方法也是一个简单的选择。

说下对 Fork 和 Join 并行计算框架的理解?

JDK 中提供了哪些并发容器?

  1. ConcurrentHashMap:这是一个线程安全的哈希表,它支持高并发的读和一定程度上的并发写。它通过将数据分割为多个段(Segment)来实现并发操作,并使用锁粒度更小的方式来提高并发性能。
  2. CopyOnWriteArrayList:这是一个线程安全的动态数组,它通过在写操作时创建一个底层数组的副本来实现安全性。这意味着写操作不会影响正在进行的读操作,从而提供了较好的读多写少场景的性能。
  3. ConcurrentLinkedQueue:这是一个线程安全的无界非阻塞队列,基于链表实现。它提供高效的并发插入和删除操作,并且不需要显式的同步。适用于多生产者多消费者的场景。
  4. ConcurrentSkipListMap和ConcurrentSkipListSet:这是基于跳表(Skip List)数据结构实现的并发容器,提供了有序的键值映射和有序的集合操作。它们支持高并发读写操作,并且具有较好的查找性能。
  5. BlockingQueue接口的实现:JDK提供了多个BlockingQueue接口的实现,如ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue等。这些队列可以用于线程间的数据交换,支持阻塞的插入和删除操作。
  6. ConcurrentLinkedDeque:这是一个线程安全的双端队列,基于链表实现。它提供高效的并发插入和删除操作,可以在队列的两端进行操作。

谈谈对 CopyOnWriteArrayList 的理解?

谈谈对 BlockingQueue 的理解?分别有哪些实现类?

谈谈对 ConcurrentSkipListMap 的理解?

Java高频面试之总纲篇

Java高频面试之集合篇

Java高频面试之异常篇

Java高频面试之并发篇

Java高频面试之SSM篇

Java高频面试之Mysql篇

Java高频面试之Redis篇

Java高频面试之消息队列与分布式篇

50道SQL面试题

奇奇怪怪的面试题

五花八门的内存溢出

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/446156.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

实时工业控制系统的创新整合:PLC4X与CnosDB的高效数据采集与存储

在当代工业自动化系统中&#xff0c;实时监测和数据分析变得至关重要。本文将介绍如何通过集成Apache PLC4X与CnosDB&#xff0c;实现对工业控制系统中的PLC设备进行高效数据采集和存储&#xff0c;为工程师们提供更强大的数据分析和监测工具。 PLC的定义 PLC是可编程逻辑控制…

【前端】vscode快捷键和实用Api整理

vscode的快捷键 创建a.html 生成模板 !回车 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><meta name"viewport" …

MySQl基础入门⑦

上一章知识内容 分析数据且区分数据类型 看下表分析数据的特征&#xff0c;根据其特征确定相应的数据类型。 分析以上表格特征&#xff0c;确定数据类型&#xff0c;并对数据进行分类。分析数据后按固定长度字符串、可变长度字符串、整数、固定精度小数和日期时间数据类型对数…

稀碎从零算法笔记Day14-LeetCode:同构字符串

题型&#xff1a;字符串、哈希表 链接&#xff1a;205. 同构字符串 - 力扣&#xff08;LeetCode&#xff09; 来源&#xff1a;LeetCode 题目描述 给定两个字符串 s 和 t &#xff0c;判断它们是否是同构的。 如果 s 中的字符可以按某种映射关系替换得到 t &#xff0c;那…

【算法面试题】-04

执行时长 def min_execution_time(n, size, tasks):a 0ans sizei 0while i < size:tmp tasks[i]a tmpif a < n:a 0else:a - ni 1ans a // nif a % n ! 0:ans 1return ans# 读取输入 n int(input()) size int(input()) tasks list(map(int, input().split()))…

Unity使用Addressable热更新

先看热更新的gif: Addressable是Unity推出的打ab包方案。不需要手动写AB打包脚手架了&#xff0c;不需要关心依赖&#xff0c;这也简化了ab热更新的流程。Addressable打包需要先将资源放入group中&#xff0c;按group来打包&#xff0c;每个group对应一个ScriptableObject的配置…

线程-创建线程的方法、线程池

1.创建线程一共有哪几种方法&#xff1f; 继承Thread类创建线程 继承Thread类&#xff0c;重写run()方法&#xff0c;在main()函数中调用子类的strat()方法 实现Runnable接口创建线程 先创建实现Runnable接口的类&#xff0c;重写run()方法&#xff0c;创建类的实例对象&#…

(南京观海微电子)——I3C协议介绍

特点 两线制总线&#xff1a;I2C仅使用两条线——串行数据线&#xff08;SDA&#xff09;和串行时钟线&#xff08;SCL&#xff09;进行通信&#xff0c;有效降低了连接复杂性。多主多从设备支持&#xff1a;I2C支持多个主设备和多个从设备连接到同一总线上。每个设备都有唯一…

靶场:sql-less-18(HTTP头注入)

本文操作环境&#xff1a;Kali-Linux 靶场链接&#xff1a;Less-18 Header Injection- Error Based- string 输入用户名和密码以后&#xff0c;我们发现屏幕上回显了我们的IP地址和我们的User Agent 用hackbar抓取POST包&#xff0c;在用户名和密码的位置判断注入点&#xff0…

【设计模式】(四)设计模式之工厂模式

1. 工厂模式介绍 工厂模式&#xff08;Factory Pattern&#xff09;是 Java 中最常用的设计模式之一。这种类型的设计模式属于创建型模式&#xff0c;它提供了一种创建对象的最佳方式。 工厂模式有三种实现方式&#xff1a; 简单工厂模式工厂方法模式抽象工厂模式 2. 工厂方…

自动创建word文档的exe文件,自定义文件名、保存路径

目录 一、exe 二、使用方法 三、代码 四、Python打包exe 一、exe 百度网盘: 链接&#xff1a;https://pan.baidu.com/s/1dyCo_iVv7fb369BHbwGjHg 提取码&#xff1a;2333 夸克网盘: 链接&#xff1a;https://pan.quark.cn/s/36b14a53cccd 二、使用方法 1. 下载完成后双…

排序(7)——非递归快排

前面我们已经写了快排用递归的方法实现&#xff0c;在数据量大的时候&#xff0c;有可能会栈溢出。这里我们尝试一下改为非递归。 区分&#xff1a; 数据结构的栈——利用的是内存中的堆空间内存的栈——利用就是内存中的栈空间——函数创建函数栈帧堆的空间是远远大于栈的空…

突破编程_前端_JS编程实例(目录导航)

1 开发目标 目录导航组件旨在提供一个滚动目录导航功能&#xff0c;使得用户可以方便地通过点击目录条目快速定位到对应的内容标题位置&#xff0c;同时也能够随着滚动条的移动动态显示当前位置在目录中的位置&#xff1a; 2 详细需求 2.1 标题提取与目录生成 组件需要能够自…

Transformer之多角度解读

Transformer 文章目录 Transformer  &#x1f449;引言&#x1f48e; 一、 自注意力机制 &#xff1a; 主要用于 长距离依赖捕捉和转换序列二、 Encoder&#xff1a;2.1 多头注意力机制&#xff1a;2.2 残差连接&#xff1a; 三、 Decoder&#xff1a;3.1 Decoder 多头注意力…

SMART PLC自适应低通滤波器(收放卷线速度滤波)

一阶低通滤波器更多内容请参考信号处理专栏相关文章,常用链接如下: 1、SMART PLC 低通滤波器和模拟量采集应用 https://rxxw-control.blog.csdn.net/article/details/136595982https://rxxw-control.blog.csdn.net/article/details/1365959822、SMART PLC双线性变换和后向差…

腾讯云服务器99元一年购买链接来了,续费也是99元

良心腾讯云推出99元一年服务器&#xff0c;新用户和老用户均可以购买&#xff0c;续费不涨价&#xff0c;续费也是99元&#xff0c;配置为轻量2核2G4M、50GB SSD盘、300GB月流量、4M带宽&#xff1a;优惠价格99元一年&#xff0c;续费99元&#xff0c;官方活动页面 txybk.com/g…

【STM32】STM32F4中USART的使用方法和Printf的重定义(基于CubeMX和Keil)

文章目录 一、前言二、STM32CubeMX生成代码2.1 选择芯片2.2 配置相关模式2.3 生成代码 三、Keil重定义Printf3.1 勾选“UseMicroLIB”3.2 添加头文件和修改fputc和fgetc 四、测试Printf的效果4.1 字符串测试4.2 格式化输出测试 五、存在问题的解决方法5.1 检查串口号是否一致5.…

由于找不到vcruntime140.dll无法继续执行的多种解决方法

最近&#xff0c;我在安装Adobe Premiere Pro&#xff08;以下简称PR&#xff09;时遇到了一个问题&#xff0c;即无法找到vcruntime140.dll文件。这可能导致某些应用程序无法正常启动或运行&#xff0c;因为vcruntime140.dll是许多基于Microsoft Visual C编译的应用程序所必需…

【中医】康复科治疗与中医养生(针灸、理疗、足浴)

程序员生活指南之 【中医】康复治疗与中医养生&#xff08;针灸、理疗、足浴&#xff09; 文章目录 1、康复科室2、中医与养生3、中医康复技术 1、康复科室 什么是康复科&#xff1f; 大部分医院都有康复科&#xff0c;但很多人都不知其具体是干什么的。其实&#xff0c;康复…

考研常识 | 专业硕士与学术硕士的11个区别

专业硕士与学术硕士的11个区别 对于考研学子而言&#xff0c;了解专业学位与学术学位的区别&#xff0c;是报考的第一步。学术学位研究生一般都是全日制的&#xff0c;而专业学位研究生的学习方式还分为即全日制与非全日制两种。这篇文章将带大家认识全日制专业学位与全日制学术…