指的是多个线程同时访问共享资源时,如果没有正确同步,可能会出现数据错误、程序异常或逻辑混乱的情况
例如:
public class Counter {
private int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
如果多个线程同时调用increment()
方法,预期结果是每次加1,但并发场景下会出现 加了多次结果却没变或者变少的 情况。
这是因为:
count++ 实际上分为三步:
1. 读取 count 的值
2. +1
3. 写回 count
多个线程可能都在几乎同时读取到相同的值,执行 +1 后写回,这就会造成“丢失更新”,即所谓的 线程不安全“
加锁同步:synchronized
、ReentrantLock
使用原子类:如 AtomicInteger
使用并发容器:ConcurrentHashMap
、CopyOnWriteArrayList
可见性:具有可见性是指一个线程对共享变量的修改,另外一个线程能够立刻看到
一个线程对共享变量的修改,对另一个线程不可见。
volatile boolean running = true;
Thread t1 = new Thread(() -> {
while (running) {
// do something
}
});
如果没有volatile
,另一个线程把running = false;
改,但t1
可能永远看不到这个修改
原子性:即一个操作或者多个操作,要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行
像 i++
这样的操作在多线程下会出现“丢失更新”问题
count++; // 实际包含3步操作
1. 读取 count 的值
2. +1
3. 写回 count
多个线程同时读写一个变量时,操作被中断或交叉执行,结果就会出错
有序性:即程序执行的顺序按照代码的先后顺序执行
变量可能在尚未完成初始化时就被另一个线程访问。
Room room = new Room();
这里的 room
是一个 引用变量,它并不直接存储对象本身,而是存储“指向堆内存中实际对象的地址”。
可以理解成:
room
是遥控器(引用)new Room()
创建的是电视(对象)JVM 在执行 room = new Room();
时,正常流程是:
1. 在堆内存中分配空间
2. 执行构造方法(初始化对象)
3. 将地址赋值给变量 room(即引用)
但为了性能优化,JVM 可能会把“步骤3”提前到“步骤2”之前:
1. 分配内存
2. 将地址赋值给变量 room(引用赋值)
3. 执行构造方法(对象还没初始化 )
引用变量先获得了堆内存地址(即对象的引用),但这块内存中对应的对象还没有被完全初始化。
在执行程序时,为了提高性能,编译器和处理器常常会对指令做重排序
代码示例:
public class SyncInstanceMethodDemo {
public synchronized void syncMethod() {
System.out.println(Thread.currentThread().getName() + " 进入实例同步方法");
try { Thread.sleep(500); } catch (InterruptedException ignored) {}
System.out.println(Thread.currentThread().getName() + " 退出实例同步方法");
}
public static void main(String[] args) {
SyncInstanceMethodDemo demo = new SyncInstanceMethodDemo();
new Thread(() -> demo.syncMethod(), "T1").start();
new Thread(() -> demo.syncMethod(), "T2").start();
}
}
运行结果:
T1 进入实例同步方法
T1 退出实例同步方法
T2 进入实例同步方法
T2 退出实例同步方法
锁对象是当前实例对象(this
)。
多个线程调用同一个实例的同步方法时会被串行执行。
代码示例:
public class SyncBlockDemo {
private final Object lock = new Object();
public void syncBlock() {
synchronized (lock) {
System.out.println(Thread.currentThread().getName() + " 进入同步代码块");
try { Thread.sleep(500); } catch (InterruptedException ignored) {}
System.out.println(Thread.currentThread().getName() + " 退出同步代码块");
}
}
public static void main(String[] args) {
SyncBlockDemo demo = new SyncBlockDemo();
new Thread(() -> demo.syncBlock(), "T3").start();
new Thread(() -> demo.syncBlock(), "T4").start();
}
}
运行结果:
T3 进入同步代码块
T3 退出同步代码块
T4 进入同步代码块
T4 退出同步代码块
解释运行过程:
SyncBlockDemo
对象,只有 一个 lock
对象。demo.syncBlock()
。synchronized (lock)
时会尝试获取 lock 的锁。代码示例:
public class StaticSyncMethodDemo {
public static synchronized void staticSyncMethod() {
System.out.println(Thread.currentThread().getName() + " 进入静态同步方法");
try { Thread.sleep(500); } catch (InterruptedException ignored) {}
System.out.println(Thread.currentThread().getName() + " 退出静态同步方法");
}
public static void main(String[] args) {
new Thread(() -> StaticSyncMethodDemo.staticSyncMethod(), "T5").start();
new Thread(() -> StaticSyncMethodDemo.staticSyncMethod(), "T6").start();
}
}
运行结果:
T5 进入静态同步方法
T5 退出静态同步方法
T6 进入静态同步方法
T6 退出静态同步方法
说明:
静态同步方法锁住类的 Class对象,两个线程顺序执行。
静态同步方法锁的是“类级别的锁”,也就是 JVM 为当前类创建的唯一 Class
对象,如 Example.class
,不论你 new 多少个对象,这个锁都是一样的
代码示例:
import java.util.concurrent.TimeUnit;
public class VolatileExample1 {
private static volatile boolean stop = false;
public static void main(String[] args) {
// Thread-A
new Thread(() -> {
while (!stop) {
// 模拟一些工作
}
System.out.println("3: " + Thread.currentThread().getName() + " 停止了");
}, "Thread A").start();
// Thread-main
try {
TimeUnit.SECONDS.sleep(1);
System.out.println("1: 主线程等待一秒...");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("2: 将 stop 变量设置为 true");
stop = true;
}
}
运行结果:
1: 主线程等待一秒...
2: 将 stop 变量设置为 true
3: Thread A 停止了
代码示例:
public class VolatileOrderExample {
int a = 0;
volatile boolean flag = false;
public void writer() {
a = 1; // 写共享变量a
flag = true; // 写 volatile变量
}
public void reader() {
if (flag) { // 读 volatile变量
System.out.println(a); // 有序性保证下,这里一定会输出1
}
}
public static void main(String[] args) {
VolatileOrderExample example = new VolatileOrderExample();
Thread t1 = new Thread(example::writer);
Thread t2 = new Thread(example::reader);
t1.start();
t2.start();
}
}
a = 1
在 flag = true
之前执行;flag = true
是 volatile
写;flag
(volatile
读)为 true 后,根据 happens-before 原则,a = 1 的写入对该线程可见。volatile
:可能会出现 flag == true
,但 a == 0
的情况(由于指令重排序或可见性问题)。
final
是 Java 的一个修饰符,用于 修饰类、方法和变量,表示不可更改的含义:
使用位置 | 表示含义 |
---|---|
类 | 不能被继承 |
方法 | 不能被重写(可以重载) |
变量 | 值不能被修改(常量) |
被 final
修饰的类不能被继承。
final class Animal {
public void run() {
System.out.println("Animal is running.");
}
}
// 错误示例:
// class Dog extends Animal {} // 报错:不能继承 final 类
被 final
修饰的方法不能被子类重写(Override)。
class Parent {
public final void sayHello() {
System.out.println("Hello from parent.");
}
}
class Child extends Parent {
// 报错:不能重写 final 方法
// public void sayHello() {}
}
变量值一旦赋值之后不能再更改,即表示常量。
类型 | 示例 | 特点 |
---|---|---|
final 局部变量 | 方法内声明的 final 变量 | 赋值后不可改,常用于 lambda |
final 成员变量 | 类中的 final 字段 | 一般和构造器搭配赋值 |
final 静态变量 | static final 修饰 |
全局常量,命名全大写 MAX_VALUE |
public class Demo {
final int age = 18;
static final String SCHOOL_NAME = "OpenAI Academy";
public void test() {
final int x = 100;
// x = 200; // 报错:不能再赋值
}
}
final List<String> list = new ArrayList<>();
list.add("hello"); // 合法
// list = new ArrayList<>(); // 报错