Java多线程编程中同步Synchronized

前言

上一讲我们讲解了多线程的基础知识,多线程优点很多,但是也有缺点,比如:

  1. 线程创建和销毁的开销:创建和销毁线程需要一定的系统资源,这会导致额外的开销。特别是在频繁创建和销毁线程的情况下,开销可能会变得显著。
  2. 线程安全问题:多线程并发执行可能会导致数据竞争和不一致的问题。为了保证数据的安全性,需要使用同步机制和锁来避免这种情况。
  3. 资源占用问题:多线程需要占用更多的内存和CPU资源。在内存资源有限的情况下,过多的线程可能会导致系统资源的耗尽。
  4. 线程通信问题:多个线程之间需要进行通信和协调,以完成共同的任务。线程之间的通信机制需要合理设计和使用,以避免出现错误和混乱。
  5. 死锁问题:当多个线程相互等待对方释放资源时,可能会导致程序的死锁。死锁问题需要谨慎处理,以避免程序陷入无法继续执行的状态

所以本讲,为了解决线程不安全问题,将讲解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关键字后面的对象必须是线程安全的共享对象。
  • 同步代码块中的代码应该尽可能地短小,避免长时间占用线程导致其他线程长时间等待。
  • 同步代码块中不能抛出异常,否则可能导致线程中断和死锁等问题。

相关推荐
夜色呦1 小时前
现代电商解决方案:Spring Boot框架实践
数据库·spring boot·后端
爱敲代码的小冰1 小时前
spring boot 请求
java·spring boot·后端
java小吕布2 小时前
Java中的排序算法:探索与比较
java·后端·算法·排序算法
Goboy3 小时前
工欲善其事,必先利其器;小白入门Hadoop必备过程
后端·程序员
李少兄3 小时前
解决 Spring Boot 中 `Ambiguous mapping. Cannot map ‘xxxController‘ method` 错误
java·spring boot·后端
代码小鑫3 小时前
A031-基于SpringBoot的健身房管理系统设计与实现
java·开发语言·数据库·spring boot·后端
Json____3 小时前
学法减分交管12123模拟练习小程序源码前端和后端和搭建教程
前端·后端·学习·小程序·uni-app·学法减分·驾考题库
monkey_meng4 小时前
【Rust类型驱动开发 Type Driven Development】
开发语言·后端·rust
落落落sss4 小时前
MQ集群
java·服务器·开发语言·后端·elasticsearch·adb·ruby
大鲤余4 小时前
Rust,删除cargo安装的可执行文件
开发语言·后端·rust