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

相关推荐
jdbcaaa2 小时前
Go 语言 runtime 包的使用与注意事项
开发语言·后端·golang·runtime
青云计划9 小时前
知光项目知文发布模块
java·后端·spring·mybatis
Victor3569 小时前
MongoDB(9)什么是MongoDB的副本集(Replica Set)?
后端
Victor3569 小时前
MongoDB(8)什么是聚合(Aggregation)?
后端
yeyeye11110 小时前
Spring Cloud Data Flow 简介
后端·spring·spring cloud
Tony Bai11 小时前
告别 Flaky Tests:Go 官方拟引入 testing/nettest,重塑内存网络测试标准
开发语言·网络·后端·golang·php
+VX:Fegn089511 小时前
计算机毕业设计|基于springboot + vue鲜花商城系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
程序猿阿伟11 小时前
《GraphQL批处理与全局缓存共享的底层逻辑》
后端·缓存·graphql
小小张说故事12 小时前
SQLAlchemy 技术入门指南
后端·python
识君啊12 小时前
SpringBoot 事务管理解析 - @Transactional 的正确用法与常见坑
java·数据库·spring boot·后端