前言
上一讲我们讲解了多线程的基础知识,多线程优点很多,但是也有缺点,比如:
- 线程创建和销毁的开销:创建和销毁线程需要一定的系统资源,这会导致额外的开销。特别是在频繁创建和销毁线程的情况下,开销可能会变得显著。
- 线程安全问题:多线程并发执行可能会导致数据竞争和不一致的问题。为了保证数据的安全性,需要使用同步机制和锁来避免这种情况。
- 资源占用问题:多线程需要占用更多的内存和CPU资源。在内存资源有限的情况下,过多的线程可能会导致系统资源的耗尽。
- 线程通信问题:多个线程之间需要进行通信和协调,以完成共同的任务。线程之间的通信机制需要合理设计和使用,以避免出现错误和混乱。
- 死锁问题:当多个线程相互等待对方释放资源时,可能会导致程序的死锁。死锁问题需要谨慎处理,以避免程序陷入无法继续执行的状态
所以本讲,为了解决线程不安全问题,将讲解Java另一个关键字Synchronized,Synchronized是Java语言提供的一种内置的线程同步机制,它可以用来解决多线程并发访问共享资源时的线程安全问题。Synchronized可以用于同步方法和同步代码块,确保同一时间只有一个线程可以访问共享资源。
一、Synchronized概述
在Java多线程编程中,Synchronized关键字是一种重要的同步机制,用于实现线程间的互斥和同步。它提供了一种在多个线程之间共享资源的安全保障,确保同一时刻只有一个线程可以访问被保护的代码块或方法。Synchronized的使用可以避免多线程并发导致的数据竞争和不一致
二、Synchronized的原理和机制
1. Synchronized的内部机制
内部机制,Synchronized通过对象头中的标记位来实现线程间的互斥和同步。当一个线程进入synchronized修饰的代码块或方法时,它会尝试获取对应的锁。如果该锁被其他线程持有,则该线程进入等待状态,直到持有锁的线程释放锁。一旦该线程获得锁,其他尝试获取该锁的线程将会被阻塞,直到该线程释放锁。
2. Synchronized的锁定机制
在Java中,Synchronized使用了可重入的锁机制。这意味着一个线程可以多次获得同一个锁,只要在每次获得锁后都释放该锁,就不会发生死锁。另外,如果一个线程在等待获取锁的过程中另一个线程释放了该锁,那么该线程将会被唤醒并获取到该锁。
3. Synchronized的等待和通知机制
在Java中,Synchronized提供了等待和通知机制。当一个线程进入等待状态时,它会释放所持有的锁,其他等待的线程有机会获取该锁并继续执行。当持有锁的线程释放锁时,它会通知等待队列中的某个线程获取该锁并继续执行。这种机制可以有效地协调多个线程之间的协同工作。
三、Synchronized的使用方法
1.方法级别的同步
使用Synchronized修饰方法时,整个方法都被标记为同步方法。在调用该方法时,只有一个线程能够进入该方法执行,其他线程需要等待当前线程执行完毕后才能继续执行。
arduino
public synchronized void synchronizedMethod() {
// 同步方法的代码
}
2.代码块级别的同步
使用Synchronized关键字加上括号,后面跟上需要同步的对象的引用,可以用来标记某个代码块为同步代码块。在同一时刻只能有一个线程执行该代码块,其他线程需要等待当前线程执行完毕后才能继续执行。
csharp
public void someMethod() {
synchronized (this) {
// 同步代码块的代码
}
}
3.使用Synchronized关键字修饰静态方法
使用Synchronized修饰静态方法时,多个线程可以同时访问该静态方法,但是每次只能有一个线程执行该静态方法。其他线程需要等待当前线程执行完毕后才能继续执行
arduino
public static synchronized void synchronizedStaticMethod() {
// 同步静态方法的代码
}
四、线程不安全、不安全例子说明
我们模拟是银行账户的转账问题。假设有两个账户,A和B,初始金额分别为A: 1000, B: 500。现在需要编写一个多线程程序,实现从A向B转移100的操作。
1.线程不安全
csharp
public class BankAccount {
private int balance;
public BankAccount(int balance) {
this.balance = balance;
}
public void withdraw(int amount) {
if (amount > balance) {
System.out.println("Insufficient balance");
} else {
balance -= amount;
System.out.println("Withdrawal successful");
}
}
public void deposit(int amount) {
balance += amount;
System.out.println("Deposit successful");
}
public int getBalance() {
return balance;
}
}
public class TransferThread extends Thread {
private BankAccount fromAccount;
private BankAccount toAccount;
private int amount;
public TransferThread(BankAccount fromAccount, BankAccount toAccount, int amount) {
this.fromAccount = fromAccount;
this.toAccount = toAccount;
this.amount = amount;
}
@Override
public void run() {
if (fromAccount.getBalance() >= amount) {
fromAccount.withdraw(amount);
toAccount.deposit(amount);
System.out.println("Transfer successful");
} else {
System.out.println("Insufficient balance in account " + fromAccount);
}
}
}
在上述代码中,两个线程同时对同一个账户进行操作,一个线程从A账户取款100,另一个线程向B账户存款100。如果没有适当的同步措施,就可能出现线程安全问题。例如,当一个线程在执行withdraw()方法时,另一个线程同时执行deposit()方法,导致B账户的存款金额被重复计算,从而导致B账户的实际金额多于预期金额。
运行结果:
2.使用使用Synchronized
csharp
public class BankAccount {
private int balance;
public BankAccount(int balance) {
this.balance = balance;
}
public synchronized void withdraw(int amount) {
if (amount > balance) {
System.out.println("Insufficient balance");
} else {
balance -= amount;
System.out.println("Withdrawal successful");
}
}
public synchronized void deposit(int amount) {
balance += amount;
System.out.println("Deposit successful");
}
public synchronized int getBalance() {
return balance;
}
}
public class TransferThread extends Thread {
private BankAccount fromAccount;
private BankAccount toAccount;
private int amount;
public TransferThread(BankAccount fromAccount, BankAccount toAccount, int amount) {
this.fromAccount = fromAccount;
this.toAccount = toAccount;
this.amount = amount;
}
@Override
public void run() {
synchronized (fromAccount) {
if (fromAccount.getBalance() >= amount) {
fromAccount.withdraw(amount);
toAccount.deposit(amount);
System.out.println("Transfer successful");
} else {
System.out.println("Insufficient balance in account " + fromAccount);
}
}
}
}
在BankAccount类中,将withdraw()、deposit()和getBalance()方法都标记为synchronized,确保同一时刻只有一个线程可以访问这些方法。在TransferThread类中,使用synchronized块来确保在执行转账操作时只有一个线程可以访问fromAccount对象。这样可以避免多个线程同时对同一个账户进行操作,确保线程安全。
运行结果:
总结
Synchronized是Java多线程编程中的关键,类似多线程的一把锁,它提供了一种简单而有效的机制来确保线程安全和避免竞争条件。通过使用Synchronized关键字,我们可以实现方法级别的同步和代码块级别的同步,以保护共享资源并确保同一时刻只有一个线程可以访问这些资源。
不过在使用Synchronized关键字修饰代码块时,需要注意以下几点:
- Synchronized关键字后面的对象必须是线程安全的共享对象。
- 同步代码块中的代码应该尽可能地短小,避免长时间占用线程导致其他线程长时间等待。
- 同步代码块中不能抛出异常,否则可能导致线程中断和死锁等问题。