Java线程二

Java中的同步(Synchronization)是一种机制,用于控制多个线程对共享资源的访问,确保线程按照一定的顺序和规则进行操作,避免出现竞态条件(Race Condition)和数据不一致的情况。

在多线程环境中,当多个线程同时访问共享资源时,可能会出现数据竞争的问题,导致程序的执行结果不确定或不正确。同步机制通过加锁来限制同时访问共享资源的线程数量,保证每次只有一个线程可以访问共享资源,从而避免竞态条件。

在Java中,可以通过以下两种方式实现线程同步:

使用synchronized关键字:

可以使用synchronized关键字修饰方法或代码块,将其标记为同步方法或同步块。当线程进入同步方法或同步块时,会自动获取该方法或块的锁,其他线程必须等待锁的释放才能执行相应的代码。

例如:

public synchronized void synchronizedMethod() {
    // 同步的代码块
}

public void someMethod() {
    synchronized (this) {
        // 同步的代码块
    }
}

使用Lock接口和ReentrantLock类:

Java提供了Lock接口和ReentrantLock类,用于实现更灵活的同步机制。与synchronized关键字相比,Lock接口提供了更多的功能,如可重入性、可中断性、公平性等。

例如:

Lock lock = new ReentrantLock();

public void someMethod() {
    lock.lock();
    try {
        // 同步的代码块
    } finally {
        lock.unlock();
    }
}

死锁(Deadlock)

指的是多个线程在争夺资源时发生相互等待的情况,导致所有线程都无法继续执行下去,从而造成系统的僵死状态。死锁通常发生在以下条件满足时:

  • 互斥条件:资源只能被一个线程占用,其他线程必须等待释放。
  • 请求与保持条件:一个线程持有资源的同时,又请求其他线程持有的资源。
  • 不可剥夺条件:已经分配给一个线程的资源不能被强制性地剥夺。
  • 循环等待条件:多个线程形成循环等待资源的关系。

为了避免死锁,可以采取以下几种策略:

  • 避免使用多个锁:尽量减少使用多个锁来降低发生死锁的概率。
  • 按照固定的顺序获取锁:规定线程获取多个锁的顺序,使得所有线程按照相同的顺序获取锁,减少死锁的可能性。
  • 使用超时机制:在获取锁的过程中设置超时时间,超过一定时间未获取到锁则放弃或重试。
  • 使用资源分配图:建立资源分配图,并通过检测图中的环来判断是否有可能发生死锁,从而采取相应的措施。

总之,同步机制是保证多线程环境下数据一致性和线程安全性的重要手段。而死锁则是多线程编程中需要特别注意和避免的问题,合理的资源管理和锁使用是避免死锁的关键。

举例说明

让我们通过一个简单的示例来说明同步和死锁的概念。

同步的示例:

假设有一个银行账户(BankAccount)类,多个线程同时对该账户进行存款操作。为了保证存款操作的线程安全性,我们可以使用同步机制来进行控制。

public class BankAccount {
    private int balance;
    
    public BankAccount(int initialBalance) {
        this.balance = initialBalance;
    }
    
    public synchronized void deposit(int amount) {
        balance += amount;
    }
}

在上述示例中,我们使用了synchronized关键字修饰了deposit方法,将其标记为同步方法。这样一来,当多个线程同时调用deposit方法时,会依次获取该方法所属对象的锁,确保每次只有一个线程可以执行deposit方法,避免并发访问造成的数据不一致问题。

死锁的示例:

假设有两个资源,分别是资源A和资源B,而有两个线程同时需要获取这两个资源。如果线程1先获取了资源A,然后尝试获取资源B,同时线程2先获取了资源B,然后尝试获取资源A,那么它们将陷入相互等待的状态,导致死锁的发生。

public class DeadlockExample {
    private static final Object resourceA = new Object();
    private static final Object resourceB = new Object();
    
    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            synchronized (resourceA) {
                System.out.println("Thread 1 acquired resource A");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (resourceB) {
                    System.out.println("Thread 1 acquired resource B");
                }
            }
        });
        
        Thread thread2 = new Thread(() -> {
            synchronized (resourceB) {
                System.out.println("Thread 2 acquired resource B");
                synchronized (resourceA) {
                    System.out.println("Thread 2 acquired resource A");
                }
            }
        });
        
        thread1.start();
        thread2.start();
    }
}

在上述示例中,线程1首先获取资源A,然后尝试获取资源B;而线程2首先获取资源B,然后尝试获取资源A。这样一来,两个线程互相等待对方释放资源,造成死锁的发生。

为了避免死锁,我们可以改变线程获取资源的顺序,确保所有线程按照相同的顺序获取资源。例如,让线程1先获取资源A,再获取资源B,而线程2则相反,先获取资源B,再获取资源A。这样就可以避免资源的循环等待,从而避免死锁的发生。