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。这样就可以避免资源的循环等待,从而避免死锁的发生。