Java线程和共享资源

在Java编程中,线程是一种执行程序的方式,它可以同时运行多个线程,以实现并发执行的效果。当多个线程同时访问和修改共享资源时,就会发生线程安全问题。为了保证共享资源的正确访问和修改,我们需要使用同步机制来确保线程之间的互斥访问。

共享资源的问题

共享资源是指多个线程共同访问和修改的变量、对象或数据结构。当多个线程同时读写共享资源时,可能会出现以下问题:

  1. 竞态条件:多个线程对共享资源的访问顺序不确定,导致结果不同于预期。
  2. 数据不一致:多个线程同时修改共享资源,导致数据的状态不一致。
  3. 死锁:多个线程相互等待对方释放资源,导致程序无法继续执行。

为了解决这些问题,Java提供了多种同步机制,包括使用synchronized关键字、使用锁(Lock)对象等。

使用synchronized关键字

synchronized关键字是Java中最常用的同步机制,它可以用来修饰方法或代码块,从而实现对共享资源的同步访问。

同步方法

使用synchronized关键字修饰方法可以确保同一时间只有一个线程执行该方法。示例代码如下:

public class Counter {
    private int count;

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

在上述代码中,increment方法被声明为synchronized,这意味着每次只能有一个线程执行该方法。当一个线程正在执行该方法时,其他线程将被阻塞,直到该线程执行完毕。

同步代码块

除了修饰方法外,还可以使用synchronized关键字修饰代码块,以实现对共享资源的同步访问。示例代码如下:

public class Counter {
    private int count;
    private Object lock = new Object();

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

在上述代码中,increment方法中的代码块被synchronized修饰,它使用了一个lock对象作为同步锁。当一个线程获取到该锁时,其他线程将被阻塞,直到该线程释放锁。

使用锁对象

除了synchronized关键字外,Java还提供了Lock接口及其实现类,用于实现更复杂的同步需求。

使用ReentrantLock

ReentrantLockLock接口的一个实现类,它提供了更灵活的同步控制。示例代码如下:

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

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

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

在上述代码中,lock对象被用作同步锁,并通过lock方法获取锁,通过unlock方法释放锁。与synchronized不同,Lock接口提供了更灵活的锁获取和释放方式,可以更好地控制同步的粒度。

线程间通信

当多个线程共享资源时,有时需要线程之间进行协作,例如一个线程等待另一个线程完成某个操作后再继续执行。

Java提供了waitnotifynotifyAll方法来实现线程间的通信。这些方法必须在synchronized块内部调用,因为它们依赖于同步监视器。

wait和notify

wait方法使当前线程等待,并释放持有的锁。notify方法唤醒一个等待的线程。

示例代码如下:

public class SharedResource {
    private int count;
    private Object lock = new Object();

    public void increment() throws InterruptedException {