Code前端首页关于Code前端联系我们

Java线程安全面试题

terry 2年前 (2023-09-25) 阅读数 47 #后端开发

线程安全有以下几种实现方式:

Immutable
不可改变(Immutable)的对象一定是线程安全的,不需要实现线程安全措施。只要正确构造不可变对象,您就不会在多个线程中看到它们处于不一致的状态。在多线程环境中,应尽可能使对象不可变,以保证线程安全。

不可变类型:

由关键字修饰的基本数据类型
String
枚举类型
Number 的一些子类,如 Long 和 Double Numeric Pack 等。数据类型。但是原子类 AtomicInteger 和 AtomicLong 都是数字,可以互换。
对于集合类型,可以使用 () 方法获取不可变集合。

public class ImmutableExample {

public static void main(String[] args) {
    Map<String, Integer> map = new HashMap<>();
    Map<String, Integer> unmodifiableMap = (map);
    ("a", 1);
}

}
“主”线程上的异常 java.lang.UnsupportedOperationException

at java.util.Collections$()
at ImmutableExample.main()

() 首先复制原始集合,任何需要修改集合的方法都会抛出异常。 。 。

publication V put(K, value V) {

throw new UnsupportedOperationException();

}
互斥锁同步
同步和ReentrantLock。

非阻塞同步
互斥同步的主要问题是线程阻塞和唤醒带来的性能问题,所以这种同步也称为阻塞同步。

互斥同步是一种悲观并发策略。人们相信,只要不采取正确的同步步骤,问题就总会发生。无论是否存在共享数据的竞争,都必须加锁(这里讨论的是概念模型,实际上虚拟机优化了很大一部分不必要的加锁)、用户态内核态转换、锁计数器维护和Ensure有阻塞的线程需要唤醒等操作。

  1. CAS
    随着硬件指令集的发展,我们可以采用基于冲突检测的乐观并发策略:先执行操作,如果没有其他线程竞争共享数据,则操作成功,否则,步骤 - 采取补偿步骤(继续尝试,直到成功)。这种乐观并发策略的很多实现都不需要线程被阻塞,因此这种同步操作称为非阻塞同步。

乐观锁需要两个操作步骤,并且冲突检测是原子的。互斥同步不再用于保证这一点,只能在硬件中完成。硬件支持的最典型的原子操作是:比较和交换(CAS)。 CAS指令需要3个操作数,分别是V的内存地址、A的预期旧值和B的新值。执行操作时,只有当V的值相等时,才会将V的值更新为B A. 包从 Unsafe 类调用 CAS 操作。

以下代码使用AtomicInteger来执行增量操作。

private AtomicInteger cnt = new AtomicInteger();

public void add() {

();

}
下面的代码是incrementAndGet('t)的源码,获取callsAndGet('t)。

public final intincrementAndGet() {

return unsafe.getAndAddInt(this, valueOffset, 1) + 1;

}
下面的代码是 getAndAddInt() 的源代码。 var1表示对象的内存地址,var2表示相对于对象内存地址的字段偏移量,var4表示要添加到运算中的值。 ,为1。通过getIntVolatile(var1,var2)获取旧的期望值,通过调用compareAndSwapInt()进行CAS比较。如果该字段内存地址中的值等于var5,则将内存地址var1+var2的变量更新为var5+var4。

可以看到getAndAddInt()是循环执行的。如果有冲突,则不断重试。

public final int getAndAddInt(Object var1, long var2, int var4) {

int var5;
do {
    var5 = (var1, var2);
} while(!(var1, var2, var5, var5 + var4));

return var5;

}

  1. ABA
    如果读取 B 时变量的值为 A,如果稍后该值发生更改,则该值会更改回到A,CAS操作会错误地认为曾经发生过变化。

包提供了一个标记为AtomicStampedReference的原子引用类来解决这个问题,它可以通过控制变量值的版本控制来保证CAS的正确性。大多数情况下,ABA问题不会影响程序的正确并发性。如果需要解决 ABA 问题,切换到传统的互斥同步可能比原子类更高效。

无同步方案
为了保证线程安全,不需要同步。如果该方法不涉及数据共享,则不应需要同步步骤来确保正确性。

  1. 堆是封闭的
    当多个线程以同样的方式访问局部变量时,不会出现线程安全问题,因为局部变量存储在虚拟机的堆中,成为线程私有的。 ? (参见本地存储)
    如果一段代码中所需的数据必须与其他代码共享,那么请确保引用该数据的代码能够保证在同一个线程中执行。如果可以保证的话,我们可以限制同一个线程的连接数据的查看次数。这样,我们就可以保证在不同步的情况下,线程之间不存在数据争用的问题。

满足此特性的应用并不常见。大多数使用消费队列的架构模式(例如“生产者-消费者”模型)将尝试在单个线程中消费产品。最重要的应用示例之一是经典Web交互模型中的“Thread-per-Request”处理方法。这种处理方法的广泛应用使得许多Web服务器应用程序都可以使用线程。本地存储解决线程安全问题。

可以使用类来实现线程本地存储功能。

以下代码,在thread1中将threadLocal设置为1,在thread2中将threadLocal设置为2。一段时间后,thread1读取threadLocal仍然为1,不受thread2影响。

public class ThreadLocalExample {

public static void main(String[] args) {
    ThreadLocal threadLocal = new ThreadLocal();
    Thread thread1 = new Thread(() -> {
        (1);
        try {
            (1000);
        } catch (InterruptedException e) {
            ();
        }
        (());
        ();
    });
    Thread thread2 = new Thread(() -> {
        (2);
        ();
    });
    ();
    ();
}

}
1
理解ThreadLocal,请看下面的代码:

class♷ Public Class
对应的基本结构图为: 每个线程有一个对象。

/* 与该线程关联的ThreadLocal值。该映射由 ThreadLocal 类管理

  • 。 */
    threadLocals = null;

调用ThreadLocal的set(T value)方法时,首先从当前线程获取一个ThreadLocalMap对象,然后将ThreadLocal->value键值对插入到Map中。

public void set(Value T) {

Thread t = ();
ThreadLocalMap map = getMap(t);
if (map != null)
    (this, value);
else
    createMap(t, value);

}
get() 方法相同。

public T get() {

Thread t = ();
ThreadLocalMap map = getMap(t);
if (map != null) {
    ThreadLocalMap.Entry e = map.getEntry(this);
    if (e != null) {
        @SuppressWarnings("unchecked")
        T result = (T)e.value;
        return result;
    }
}
return setInitialValue();

}
ThreadLocal 理论上并不是为了解决多线程并发问题而设计的,因为不存在多线程竞争。

在某些场景下(尤其是使用线程池时),ThreadLocal由于底层数据结构的原因可能会出现内存泄漏的情况。每次使用ThreadLocal后都必须手动调用delete(),以避免经典的ThreadLocal内存泄漏。泄密甚至可能会导致您自己的业务中断。

  1. 可重入代码
    此类代码也称为纯代码。它可以在执行过程中随时中断代码并切换到其他代码执行(包括递归调用),并且控制权返回后,原程序不会导致错误。

可重入代码具有一般特征,例如不依赖于堆栈上存储的数据和公共系统资源,所有使用的状态变量都通过参数传递,并且不调用不可重入方法。

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门