Skip to content

07_Java内存模型.md

  • 什么是 Java 内存模型?

Java 内存模型(Java Memory Model, JMM)是一种抽象概念,用于定义 Java 程序中多线程之间的工作内存(Working Memory)和主内存(Main Memory)之间的交互关系。JMM 规定了在何种情况下写入操作对读取操作是可见的,以及在何种情况下操作的执行顺序是有序的。理解 Java 内存模型对于编写正确的并发程序至关重要。

  • 为什么需要 Java 内存模型?
    • 多线程并发
      • 在多线程环境中,多个线程可能同时访问共享变量,导致数据不一致的问题。Java 内存模型定义了多线程之间如何协作,以确保线程之间的可见性和有序性。
      • 例如,一个线程对共享变量进行写操作,另一个线程对共享变量进行读操作,JMM 规定了写操作对读操作的可见性,以及写操作和读操作的执行顺序。
      • 理解 Java 内存模型可以帮助我们编写正确的并发程序,避免出现线程安全问题。

不安全

不安全指的是代码在多线程环境下存在数据竞争(data race)或其他并发问题,无法保证正确性。

java
public class UnsafeExample {
    private int count = 0;

    public void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }

    public static void main(String[] args) {
        UnsafeExample example = new UnsafeExample();

        Thread t1 = new Thread(example::increment);
        Thread t2 = new Thread(example::increment);

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final count: " + example.getCount());
    }
}

在这个例子中,count 变量的访问没有任何同步措施,因此多个线程同时访问时可能会出现数据竞争,导致不安全的结果。

绝对线程安全

绝对线程安全指的是代码在任何情况下都能正确地处理多个线程的访问,不需要任何额外的同步措施。

  • Java 中的 java.util.concurrent 包中的类,如 AtomicInteger,是绝对线程安全的。
java
import java.util.concurrent.atomic.AtomicInteger;

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

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

    public int getCount() {
        return count.get();
    }

    public static void main(String[] args) {
        AbsolutelySafeExample example = new AbsolutelySafeExample();

        Thread t1 = new Thread(example::increment);
        Thread t2 = new Thread(example::increment);

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final count: " + example.getCount());
    }
}

在这个例子中,AtomicInteger 确保了对 count 变量的操作是绝对线程安全的。

相对线程安全

相对线程安全指的是代码在某些条件下是线程安全的,但需要额外的同步措施来确保在某些情况下的线程安全性。

  • Java 中的 Collections.synchronizedList 提供了一个相对线程安全的列表。
java
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;

public class RelativelySafeExample {
    private List<Integer> list = Collections.synchronizedList(new ArrayList<>());

    public void addItem(int item) {
        list.add(item);
    }

    public int getSize() {
        synchronized (list) {
            return list.size();
        }
    }

    public static void main(String[] args) {
        RelativelySafeExample example = new RelativelySafeExample();

        Thread t1 = new Thread(() -> example.addItem(1));
        Thread t2 = new Thread(() -> example.addItem(2));

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final size: " + example.getSize());
    }
}

在这个例子中,Collections.synchronizedList 确保了 add 操作的线程安全性,但 getSize 方法仍需要额外的同步措施。

线程兼容

线程兼容指的是代码本身不是线程安全的,但可以通过适当的外部同步措施来确保线程安全。

java
public class ThreadCompatibleExample {
    private int count = 0;

    public void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }

    public static void main(String[] args) {
        ThreadCompatibleExample example = new ThreadCompatibleExample();

        Runnable task = () -> {
            synchronized (example) {
                example.increment();
            }
        };

        Thread t1 = new Thread(task);
        Thread t2 = new Thread(task);

        t1.start();
        t2.start();

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        synchronized (example) {
            System.out.println("Final count: " + example.getCount());
        }
    }
}

在这个例子中,通过在外部对 increment 方法进行同步,确保了线程兼容的安全性。

线程对立

线程对立指的是代码不能在多线程环境下使用,即使添加同步措施也不能解决问题。

  • 单线程环境中的某些算法或操作,在多线程环境中无法工作。例如,某些图形处理或硬件交互操作。
java
public class ThreadOpposedExample {
    private int[] data = {1, 2, 3, 4, 5};

    public int processData() {
        // 假设这是一种不支持并发访问的操作
        return data[0] + data[1] + data[2] + data[3] + data[4];
    }

    public static void main(String[] args) {
        ThreadOpposedExample example = new ThreadOpposedExample();

        Thread t1 = new Thread(() -> {
            synchronized (example) {
                System.out.println("Thread 1 result: " + example.processData());
            }
        });

        Thread t2 = new Thread(() -> {
            synchronized (example) {
                System.out.println("Thread 2 result: " + example.processData());
            }
        });

        t1.start();
        t2.start();
    }
}

在这个例子中,即使添加了同步措施,processData 方法也可能无法在多线程环境中正确工作,假设它依赖于某些单线程特性。

总结

  • Java 内存模型定义了多线程之间的可见性和有序性规则,帮助我们编写正确的并发程序。
  • 线程安全性可以分为绝对线程安全、相对线程安全、线程兼容和线程对立四种级别,需要根据具体情况选择合适的同步措施。
  • 在编写多线程程序时,需要考虑线程安全性和性能之间的平衡,确保程序正确、高效地运行。
  • 了解 Java 内存模型和线程安全性的概念,可以帮助我们更好地理解并发编程的原理和实践。

Java 内存模型规定了多线程环境下变量的读取和写入的行为,提供了一组 happens-before 规则,确保了线程间操作的可见性和有序性。理解和应用 JMM 的规则对于实现上述不同线程安全级别至关重要。

  • 不安全:通常违反 JMM 的 happens-before 规则,导致数据竞争。
  • 绝对线程安全:通过 JMM 提供的同步机制(如 volatile、synchronized、Atomic 类)确保线程安全。
  • 相对线程安全:需要额外的同步措施来确保符合 JMM 的规则。
  • 线程兼容:通过外部同步措施使其符合 JMM 的规则。
  • 线程对立:无法通过 JMM 规则解决其线程安全问题。