Java使用synchronized修饰方法来同步线程的实例演示

 更新时间:2016年06月15日 15:16:44   作者:柳哥  
synchronized下的方法控制多线程程序中的线程同步非常方便,这里就来看一下Java使用synchronized修饰方法来同步线程的实例演示,需要的朋友可以参考下

Java中可以使用关键字synchronized进行线程同步控制,实现关键资源顺序访问,避免由于多线程并发执行导致的数据不一致性等问题。synchronized的原理是对象监视器(锁),只有获取到监视器的线程才能继续执行,否则线程会等待获取监视器。Java中每个对象或者类都有一把锁与之相关联,对于对象来说,监视的是这个对象的实例变量,对于类来说,监视的是类变量(一个类本身是类Class的对象,所以与类关联的锁也是对象锁)。synchronized关键字使用方式有两种:synchronized方法和synchronized块。这两种监视区域都和一个引入对象相关联,当到达这个监视区域时,JVM就会锁住这个引用对象,当离开时会释放这个引用对象上的锁(有异常退出时,JVM会释放锁)。对象锁是JVM内部机制,只需要编写同步方法或者同步块即可,操作监视区域时JVM会自动获取锁或者释放锁。

示例1

先来看第一个示例,在java中,同一个对象的临界区,在同一时间只有一个允许被访问(都是非静态的synchronized方法):

package concurrency;

public class Main8 {
  public static void main(String[] args) {
    Account account = new Account();
    account.setBalance(1000);
    Company company = new Company(account);
    Thread companyThread = new Thread(company);
    Bank bank = new Bank(account);
    Thread bankThread = new Thread(bank);
    System.out.printf("Account : Initial Balance: %f\n", account.getBalance());
    companyThread.start();
    bankThread.start();
    try {
      //join()方法等待这两个线程运行完成
      companyThread.join();
      bankThread.join();
      System.out.printf("Account : Final Balance: %f\n", account.getBalance());
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

/*帐户*/
class Account{
  private double balance;
  /*将传入的数据加到余额balance中*/
  public synchronized void addAmount(double amount){
    double tmp = balance;
    try {
      Thread.sleep(10);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    tmp += amount;
    balance = tmp;
  }
  /*将传入的数据从余额balance中扣除*/
  public synchronized void subtractAmount(double amount){
    double tmp = balance;
    try {
      Thread.sleep(10);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    tmp -= amount;
    balance = tmp;
  }
  public double getBalance() {
    return balance;
  }
  public void setBalance(double balance) {
    this.balance = balance;
  }
}

/*银行*/
class Bank implements Runnable{
  private Account account;
  public Bank(Account account){
    this.account = account;
  }
  @Override
  public void run() {
    for (int i = 0; i < 100; i++) {
      account.subtractAmount(1000);
    }
  }
}

/*公司*/
class Company implements Runnable{
  private Account account;
  public Company(Account account){
    this.account = account;
  }
  @Override
  public void run() {
    for (int i = 0; i < 100; i++) {
      account.addAmount(1000);
    }
  }
}

你已经开发了一个银行账户的模拟应用,它能够对余额进行充值和扣除。这个程序通过调用100次addAmount()方法对帐户进行充值,每次存入1000;然后通过调用100次subtractAmount()方法对帐户余额进行扣除,每次扣除1000;我们期望帐户的最终余额与起初余额相等,我们通过synchronized关键字实现了。

如果想查看共享数据的并发访问问题,只需要将addAmount()和subtractAmount()方法声明中的synchronized关键字删除即可。在没有synchronized关键字的情况下,打印出来的余额值并不一致。如果多次运行这个程序,你将获取不同的结果。因为JVM并不保证线程的执行顺序,所以每次运行的时候,线程将以不同的顺序读取并且修改帐户的余额,造成最终结果也是不一样的。

一个对象的方法采用synchronized关键字进行声明,只能被一个线程访问。如果线程A正在执行一个同步方法syncMethodA(),线程B要执行这个对象的其他同步方法syncMethodB(),线程B将被阻塞直到线程A访问完。但如果线程B访问的是同一个类的不同对象,那么两个线程都不会被阻塞。

示例2

演示同一个对象上的静态synchronized方法与非静态synchronized方法可以在同一时间点被多个线程访问的问题,验证一下。

package concurrency;

public class Main8 {
  public static void main(String[] args) {
    Account account = new Account();
    account.setBalance(1000);
    Company company = new Company(account);
    Thread companyThread = new Thread(company);
    Bank bank = new Bank(account);
    Thread bankThread = new Thread(bank);
    System.out.printf("Account : Initial Balance: %f\n", account.getBalance());
    companyThread.start();
    bankThread.start();
    try {
      //join()方法等待这两个线程运行完成
      companyThread.join();
      bankThread.join();
      System.out.printf("Account : Final Balance: %f\n", account.getBalance());
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}

/*帐户*/
class Account{
  /*这里改为静态变量*/
  private static double balance = 0;
  /*将传入的数据加到余额balance中,注意是用static修饰过的*/
  public static synchronized void addAmount(double amount){
    double tmp = balance;
    try {
      Thread.sleep(10);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    tmp += amount;
    balance = tmp;
  }
  /*将传入的数据从余额balance中扣除*/
  public synchronized void subtractAmount(double amount){
    double tmp = balance;
    try {
      Thread.sleep(10);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    tmp -= amount;
    balance = tmp;
  }
  public double getBalance() {
    return balance;
  }
  public void setBalance(double balance) {
    this.balance = balance;
  }
}

/*银行*/
class Bank implements Runnable{
  private Account account;
  public Bank(Account account){
    this.account = account;
  }
  @Override
  public void run() {
    for (int i = 0; i < 100; i++) {
      account.subtractAmount(1000);
    }
  }
}

/*公司*/
class Company implements Runnable{
  private Account account;
  public Company(Account account){
    this.account = account;
  }
  @Override
  public void run() {
    for (int i = 0; i < 100; i++) {
      account.addAmount(1000);
    }
  }
}

我只是把上个例子中的,balance加了static关键字修改,addAmount()方法也可以static关键字修饰。执行结果大家可以自己测试一下,每次执行都是不一样的结果!

一些总结:

  • synchronized是通过软件(JVM)实现的,简单易用,即使在JDK5之后有了Lock,仍然被广泛地使用。
  • synchronized实际上是非公平的,新来的线程有可能立即获得监视器,而在等待区中等候已久的线程可能再次等待,不过这种抢占的方式可以预防饥饿。
  • synchronized只有锁只与一个条件(是否获取锁)相关联,不灵活,后来Condition与Lock的结合解决了这个问题。
  • 多线程竞争一个锁时,其余未得到锁的线程只能不停的尝试获得锁,而不能中断。高并发的情况下会导致性能下降。ReentrantLock的lockInterruptibly()方法可以优先考虑响应中断。 一个线程等待时间过长,它可以中断自己,然后ReentrantLock响应这个中断,不再让这个线程继续等待。有了这个机制,使用ReentrantLock时就不会像synchronized那样产生死锁了。

相关文章

  • Java小程序求圆的周长和面积实例

    Java小程序求圆的周长和面积实例

    这篇文章主要介绍了首先用蒙塔卡洛算法求圆周率近似值,然后根据此近似值输出圆的周长和面积,具有一定参考价值,需要的朋友可以了解下。
    2017-09-09
  • Java日常练习题,每天进步一点点(20)

    Java日常练习题,每天进步一点点(20)

    下面小编就为大家带来一篇Java基础的几道练习题(分享)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧,希望可以帮到你
    2021-07-07
  • Spring注解配置实现过程详解

    Spring注解配置实现过程详解

    这篇文章主要介绍了Spring注解配置实现过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • Java多线程的原子性,可见性,有序性你都了解吗

    Java多线程的原子性,可见性,有序性你都了解吗

    这篇文章主要为大家详细介绍了Java多线程的原子性,可见性,有序性,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助
    2022-03-03
  • JAVA中的日期时间类用法总结

    JAVA中的日期时间类用法总结

    这篇文章主要给大家介绍了关于JAVA中日期时间类用法的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-09-09
  • 轻松掌握java责任链模式

    轻松掌握java责任链模式

    这篇文章主要帮助大家轻松掌握java责任链模式,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2016-09-09
  • Mybatis批量修改联合主键数据的两种方法

    Mybatis批量修改联合主键数据的两种方法

    最近遇上需要批量修改有联合主键的表数据,找很多资料都不是太合适,最终自己摸索总结了两种方式可以批量修改数据,对Mybatis批量修改数据相关知识感兴趣的朋友一起看看吧
    2022-04-04
  • kafka并发写大消息异常TimeoutException排查记录

    kafka并发写大消息异常TimeoutException排查记录

    这篇文章主要为大家介绍了kafka并发写大消息异常TimeoutException的排查记录及解决方案,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步
    2022-02-02
  • Java 并发编程之ForkJoin框架

    Java 并发编程之ForkJoin框架

    这篇文章主要为大家介绍了Java ForkJoin框架,具有一定的参考价值,感兴趣的小伙伴们可以参考一下,希望能够给你带来帮助,希望能够给你带来帮助
    2021-11-11
  • 前端如何传递Array、Map类型数据到Java后端

    前端如何传递Array、Map类型数据到Java后端

    这篇文章主要给大家介绍了关于前端如何传递Array、Map类型数据到Java后端的相关资料,文中通过图文介绍的非常详细,对大家的学习或者工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2024-01-01

最新评论