跳转到内容

Java线程同步

来自代码酷

Java线程同步[编辑 | 编辑源代码]

Java线程同步是多线程编程中的核心概念,用于控制多个线程对共享资源的访问,防止出现数据不一致或竞态条件(Race Condition)等问题。当多个线程同时访问和修改同一数据时,如果没有适当的同步机制,可能会导致不可预测的结果。Java提供了多种同步机制来确保线程安全。

为什么需要线程同步?[编辑 | 编辑源代码]

在多线程环境中,多个线程可能同时访问和修改共享数据。例如,两个线程同时对一个银行账户进行存款和取款操作,如果没有同步机制,可能会导致账户余额计算错误。线程同步的主要目的是:

  • 保证数据一致性:确保共享数据在任何时候都处于一致状态。
  • 避免竞态条件:防止多个线程同时修改数据导致逻辑错误。
  • 实现线程协作:协调多个线程的执行顺序。

Java中的同步机制[编辑 | 编辑源代码]

Java提供了多种同步机制,主要包括:

1. synchronized 关键字[编辑 | 编辑源代码]

`synchronized` 是Java中最基本的同步机制,可以用于方法或代码块,确保同一时间只有一个线程可以执行被同步的代码。

同步方法[编辑 | 编辑源代码]

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;
    private final Object lock = new Object();

    public void increment() {
        // 同步代码块
        synchronized (lock) {
            count++;
        }
    }
}

2. Lock 接口[编辑 | 编辑源代码]

Java 5引入了`java.util.concurrent.locks.Lock`接口,提供了比`synchronized`更灵活的锁机制,如`ReentrantLock`。

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();
        }
    }
}

3. volatile 关键字[编辑 | 编辑源代码]

`volatile` 用于确保变量的可见性,即当一个线程修改了`volatile`变量时,其他线程能立即看到最新的值。

public class SharedData {
    private volatile boolean flag = false;

    public void setFlag(boolean value) {
        flag = value;
    }

    public boolean getFlag() {
        return flag;
    }
}

4. 原子类(Atomic Classes)[编辑 | 编辑源代码]

Java提供了`java.util.concurrent.atomic`包中的原子类(如`AtomicInteger`、`AtomicLong`等),它们使用CAS(Compare-And-Swap)机制实现无锁线程安全操作。

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();
    }
}

实际应用案例[编辑 | 编辑源代码]

银行账户转账问题[编辑 | 编辑源代码]

假设有两个线程同时对一个银行账户进行存款和取款操作,如果不使用同步机制,可能会导致余额计算错误。

public class BankAccount {
    private double balance;

    public BankAccount(double initialBalance) {
        balance = initialBalance;
    }

    // 同步方法确保存款操作线程安全
    public synchronized void deposit(double amount) {
        balance += amount;
    }

    // 同步方法确保取款操作线程安全
    public synchronized void withdraw(double amount) {
        if (balance >= amount) {
            balance -= amount;
        } else {
            System.out.println("余额不足");
        }
    }

    public synchronized double getBalance() {
        return balance;
    }
}

生产者-消费者问题[编辑 | 编辑源代码]

生产者线程生成数据,消费者线程消费数据,使用`wait()`和`notify()`实现线程协作。

import java.util.LinkedList;
import java.util.Queue;

public class ProducerConsumer {
    private final Queue<Integer> queue = new LinkedList<>();
    private final int CAPACITY = 5;

    public void produce() throws InterruptedException {
        int value = 0;
        while (true) {
            synchronized (this) {
                while (queue.size() == CAPACITY) {
                    wait(); // 队列满时等待
                }
                System.out.println("生产者生产: " + value);
                queue.add(value++);
                notify(); // 唤醒消费者
                Thread.sleep(1000);
            }
        }
    }

    public void consume() throws InterruptedException {
        while (true) {
            synchronized (this) {
                while (queue.isEmpty()) {
                    wait(); // 队列空时等待
                }
                int value = queue.poll();
                System.out.println("消费者消费: " + value);
                notify(); // 唤醒生产者
                Thread.sleep(1000);
            }
        }
    }
}

线程同步的常见问题[编辑 | 编辑源代码]

死锁(Deadlock)[编辑 | 编辑源代码]

当两个或多个线程互相持有对方需要的资源时,可能会导致死锁。例如:

graph TD A[线程1持有锁A] -->|请求锁B| B[线程2持有锁B] B -->|请求锁A| A

活锁(Livelock)[编辑 | 编辑源代码]

线程不断重试某个操作,但始终无法取得进展。例如两个线程互相让出资源,导致都无法执行。

饥饿(Starvation)[编辑 | 编辑源代码]

某些线程长时间得不到执行机会,通常是由于优先级设置不合理或锁竞争不公平导致。

总结[编辑 | 编辑源代码]

Java线程同步是多线程编程的核心,合理使用`synchronized`、`Lock`、`volatile`和原子类可以确保线程安全。同时,需要注意避免死锁、活锁和饥饿等问题。在实际开发中,应根据具体场景选择合适的同步机制。

参见[编辑 | 编辑源代码]