第3课_synchronized和lock
热度🔥:24 免费课程
授课语音
Java的synchronized和Lock
一、引言
在多线程编程中,当多个线程同时访问共享资源时,可能会发生数据冲突和不一致的情况。为了解决这一问题,我们通常使用同步机制来确保同一时刻只有一个线程可以访问关键代码区域。Java 中提供了两种主要的同步机制:synchronized
和 Lock
。它们各有优缺点,适用于不同的场景。
二、synchronized关键字
1. synchronized
简介
synchronized
是 Java 内置的同步机制,它可以用来确保同一时间只有一个线程执行某个方法或代码块,从而避免数据冲突。
- 通过在方法或代码块上添加
synchronized
关键字,可以将其设为同步方法或同步代码块。 - 适合一些简单的同步场景,但控制力度不够细腻。
2. synchronized
的应用方式
synchronized
的主要应用方式有两种:
- 同步方法:将整个方法设置为同步方法。
- 同步代码块:将部分代码放在
synchronized
块中,实现更精确的控制。
3. synchronized
代码示例
示例 1:同步方法
public class SynchronizedExample {
private int count = 0;
// 定义一个同步方法,确保在同一时刻只有一个线程能执行此方法
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
示例 2:同步代码块
public class SynchronizedBlockExample {
private int count = 0;
private final Object lock = new Object();
// 使用同步代码块,锁定具体代码块
public void increment() {
synchronized (lock) { // 只有在锁内的代码才会被同步
count++;
}
}
public int getCount() {
return count;
}
}
在上述示例中,increment
方法是同步的,确保多个线程调用时,只有一个线程可以操作 count
变量。
三、Lock接口
1. Lock
的简介
Lock
是 Java 并发包(java.util.concurrent.locks
)提供的一个接口,用来实现更灵活的同步控制。与 synchronized
相比,Lock
提供了更多的控制和灵活性,例如可以尝试获取锁、可以在获取锁的过程中设置等待时间等。
2. Lock
的主要方法
lock()
:获取锁,若锁已被其他线程占用,则等待。unlock()
:释放锁。tryLock()
:尝试获取锁,立即返回true
或false
,不会阻塞。lockInterruptibly()
:在等待锁的过程中可响应中断。
3. 使用 Lock
的步骤
- 创建锁对象,例如
ReentrantLock
。 - 在代码块中获取锁(调用
lock()
)。 - 执行关键代码。
- 在
finally
块中释放锁,确保资源安全。
4. Lock
的代码示例
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private int count = 0;
private final Lock lock = new ReentrantLock();
// 使用Lock实现同步
public void increment() {
lock.lock(); // 获取锁
try {
count++;
} finally {
lock.unlock(); // 在finally块中释放锁,确保一定会执行
}
}
public int getCount() {
return count;
}
}
上述代码通过 ReentrantLock
实现了线程安全。使用 lock()
和 unlock()
进行锁的管理,可以确保只有一个线程能够访问共享资源,并且无论是否出现异常,都会释放锁。
四、synchronized和Lock的区别与比较
比较项 | synchronized |
Lock |
---|---|---|
锁机制 | 内置于 Java 语言 | 通过 java.util.concurrent.locks 提供 |
灵活性 | 较低,自动释放锁 | 较高,可手动控制锁的获取与释放 |
锁可重入性 | 支持 | 支持(如 ReentrantLock ) |
可中断性 | 不支持 | 支持 lockInterruptibly() 方法 |
超时获取锁 | 不支持 | 支持 tryLock() 方法 |
性能 | 效率较高 | 在高并发情况下更灵活 |
五、应用场景
- synchronized:适合锁的范围和生命周期比较简单的场景,尤其是内部同步。
- Lock:适合锁的控制更为复杂、需要高灵活性的场景,例如在高并发应用中使用,能够提供更细粒度的控制。
六、最佳实践
- 如果同步需求简单、明确,可以优先使用
synchronized
,其内置特性让代码更易于维护。 - 如果同步要求较高,需要精细的锁控制,例如尝试获取锁、定时获取锁等,可以使用
Lock
。 - 在使用
Lock
时,一定要注意在finally
块中释放锁,防止因异常而导致锁未被释放的问题。
七、综合实例
下面是一个综合应用了 synchronized
和 Lock
的示例,展示了如何在不同场景下选择适当的同步机制。
代码示例:使用 synchronized
和 Lock
实现并发访问
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class BankAccount {
private int balance = 1000; // 银行账户余额
private final Lock lock = new ReentrantLock(); // 创建可重入锁
// 使用synchronized确保账户存款的线程安全
public synchronized void deposit(int amount) {
balance += amount;
System.out.println(Thread.currentThread().getName() + " 存款: " + amount + " 新余额: " + balance);
}
// 使用Lock确保账户取款的线程安全
public void withdraw(int amount) {
lock.lock(); // 获取锁
try {
if (balance >= amount) {
balance -= amount;
System.out.println(Thread.currentThread().getName() + " 取款: " + amount + " 新余额: " + balance);
} else {
System.out.println(Thread.currentThread().getName() + " 余额不足,无法取款 " + amount);
}
} finally {
lock.unlock(); // 确保锁总能释放
}
}
public int getBalance() {
return balance;
}
public static void main(String[] args) {
BankAccount account = new BankAccount();
// 创建存款线程
Thread depositThread = new Thread(() -> account.deposit(500), "存款线程");
// 创建取款线程
Thread withdrawThread = new Thread(() -> account.withdraw(300), "取款线程");
depositThread.start();
withdrawThread.start();
}
}
代码说明:
deposit
方法使用synchronized
关键字,使其在执行时线程安全。withdraw
方法使用Lock
进行锁管理,这样我们可以精确地控制锁的获取和释放,并能处理余额不足的情况。- 在
main
方法中启动两个线程,分别执行存款和取款操作,测试线程安全。
通过这两个方法,我们可以看到 synchronized
和 Lock
的不同使用方式。
八、总结
synchronized
和 Lock
是 Java 中实现多线程同步的两大核心工具,各有优劣。
synchronized
使用简单,但灵活性较差,适用于简单同步。Lock
灵活性高,适合更复杂的同步控制场景。
了解并掌握它们的使用场景,能有效提高程序的并发安全性和执行效率。