【Java面试准备】共享变量问题?

从4个角度来回答:是什么?为什么?怎么解决?具体场景?

1.什么是共享变量问题?

共享变量问题是指在多线程环境中,多个线程访问和修改同一个变量时,由于线程调度的不确定性,导致变量的值可能与预期不符。具体表现:

  • 数据不一致:多个线程对共享变量的修改没有正确同步,导致变量的值被错误覆盖或更新。

  • 线程安全问题:由于缺乏同步机制,多个线程同时对共享变量进行读写操作,可能会导致数据损坏或程序崩溃。

  • 不可预测的行为:线程调度的不确定性使得程序的行为难以预测,可能导致程序在某些情况下运行正常,而在其他情况下出现错误。

2.为什么?产生原因?

(1) 线程调度的不确定性

操作系统会根据一定的策略(如时间片轮转)调度线程的执行。线程的切换是不可预测的,这可能导致多个线程对共享变量的访问顺序与预期不符。

(2) 缓存一致性问题

每个线程都有自己的工作内存(线程私有的内存空间),线程对共享变量的操作通常先在工作内存中进行,然后再同步到主内存。如果线程之间没有正确同步,可能会导致工作内存中的数据与主内存中的数据不一致。

(3) 指令重排序

为了提高执行效率,编译器和处理器可能会对代码进行指令重排序。如果对共享变量的操作被重排序,可能会导致线程看到错误的变量值。

3.怎么解决?

解决共享变量问题的关键在于确保线程之间的正确同步

☀️使用synchronized关键字

synchronized关键字可以用于方法或代码块,确保同一时间只有一个线程可以执行特定的代码段。它通过加锁机制来保证线程安全。

// 同步方法
public class Counter {
    private int count = 0;

    public synchronized void increment() {
        count++;
    }

    public synchronized int getCount() {
        return count;
    }
}


// 同步代码块
public class Counter {
    private int count = 0;

    public void increment() {
        synchronized (this) {
            count++;
        }
    }

    public int getCount() {
        synchronized (this) {
            return count;
        }
    }
}
使用volatile关键字

volatile关键字可以确保变量的可见性和禁止指令重排序但它不提供互斥性。因此,volatile通常用于简单的状态标志,而不是复杂的复合操作。

public class VolatileExample {
    private volatile boolean flag = true;

    public void run() {
        while (flag) {
            // 执行循环
        }
        System.out.println("线程退出");
    }

    public void stop() {
        flag = false;
    }
}
使用Atomic

Java提供了java.util.concurrent.atomic包中的Atomic类,如AtomicIntegerAtomicLong等,这些类通过硬件级别的原子操作来确保线程安全。

import java.util.concurrent.atomic.AtomicInteger;

public class Counter {
    private AtomicInteger count = new AtomicInteger(0);

    public void increment() {
        count.incrementAndGet();
    }

    public int getCount() {
        return count.get();
    }
}
 使用锁(Lock接口)

java.util.concurrent.locks提供了更灵活的锁机制,如ReentrantLock。与synchronized相比,Lock提供了更多的功能,如尝试锁定、可中断的锁定等。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class Counter {
    private int count = 0;
    private final Lock lock = new ReentrantLock();

    public void increment() {
        lock.lock();
        try {
            count++;
        } finally {
            lock.unlock();
        }
    }

    public int getCount() {
        lock.lock();
        try {
            return count;
        } finally {
            lock.unlock();
        }
    }
}

4. 应用场景?

 (1) 线程安全的计数器

如果没有同步机制,多个线程对同一个计数器进行增量操作时,可能会导致最终的计数值小于预期。

public class Counter {
    private int count = 0;

    public void increment() {
        count++; // 非线程安全的操作
        // 在多线程环境下,count++操作可能会被重排序或被其他线程干扰,导致结果不正确。
    }

    public int getCount() {
        return count;
    }
}
(2) 线程安全的单例模式

在单例模式中,如果没有正确同步,可能会导致多个线程同时创建多个实例。

public class Singleton {
    private static Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) { // 可能存在线程安全问题
            instance = new Singleton();
        }
        return instance;
    }
}

// ---------------------------------------------------------------


// 为了避免线程安全问题,可以使用双重检查锁定(Double-Checked Locking)模式,
// 并将instance声明为volatile。
public class Singleton {
    private static volatile Singleton instance;

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}
(3) 线程安全的缓存

在缓存实现中,如果没有正确同步,可能会导致缓存数据不一致。

public class Cache {
    private Map cache = new HashMap<>();

    public String get(String key) {
        return cache.get(key); // 非线程安全的操作
    }

    public void put(String key, String value) {
        cache.put(key, value); // 非线程安全的操作
    }
}

// -------------------------------------------------------


// 为了避免线程安全问题,可以使用ConcurrentHashMap来代替HashMap。
import java.util.concurrent.ConcurrentHashMap;

public class Cache {
    private Map cache = new ConcurrentHashMap<>();

    public String get(String key) {
        return cache.get(key);
    }

    public void put(String key, String value) {
        cache.put(key, value);
    }
}

以上!自学用,防遗忘!

你可能感兴趣的:(Java,java,面试,后端)