浅谈同步监视器之同步代码块、同步方法

 更新时间:2017年08月24日 09:44:55   投稿:jingxian  
下面小编就为大家带来一篇浅谈同步监视器之同步代码块、同步方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

如果有多个线程访问共享资源,可能会出现当一个线程没有处理完业务,然后另一个线程进入,从而导致共享资源出现不安全的情况。

日常例子:银行取钱,A和B有拥有同一个银行账户,A用存折在柜台取钱,B在取款机取钱。取钱有两个关键步骤:

(1)判断账户里的钱的余额是否大于所取钱数

(2)如果大于所取钱数,则账户最终所剩余额 = 余额 - 所取钱数。

如果没有线程同步的情况下,我们假设这一种情况,这个共同的账户里共1000元。

(1)A B同时去取600元,A所在线程执行到上面的第一个步骤,判断所取钱数小于现有余额,CPU时间片用完。

(2)这时B进来到第一个步骤,同样是执行判断,因为A只执行完第一步骤,没有执行减法,这时现有余额还是1000元。

(3)由于在CPU分配的时间里他接着完成了减法操作。这时账户余额为1000 - 600 = 400。成功取出600元。

(4)最后A接着之前执行的步骤,去做减法操作, 账户余额为 -200 = 400 - 600。

到这里,我只想说为什么,是什么银行可以允许你这么做, 当然,除非银行是你家开的。

总之银行不可能让这种情况发生,所以我们的伟大先贤们就想到线程同步,其实很简单,你也能想到。如果让这两个步骤同时完成,不可分开,问题也就迎刃而解。

下面就说到在JAVA中同步代码的实现:

涉及概念:同步监视器,是一个普通的java对象,同一个同步监视器如果一个线程拿到,则其他线程就没有办法拿到。好像是一个房门里只有唯一的一把钥匙, 不能复制。如果一个人拿着它进入房门,其他人只能在外面等候。等他出来你获得了它,你才能进入房间。

下面的代码如果没有做线程同步操作(同步代码块、同步方法、同步锁)结果是如下:

Thread-1------判断所取钱数是否大于余额------
Thread-0------判断所取钱数是否大于余额------
Thread-0======做减法操作,取出现金======
Thread-1======做减法操作,取出现金======

很显然线程1的那两步没有同时完成。

下面的几种方法可以实现两步同时完成。

1、同步代码块:

public class ThreadTest {

  public static void main(String[] args){
Thread t1 = new Thread1(); //线程1
Thread t2 = new Thread1();//线程2
t1.start();
t2.start();
  }
}

class Thread1 extends Thread{

  @Override
  public void run() {
super.run();
try {

  BeTested b = new BeTested(); // 这地方,因为这个例子中同步监视器 obj 是线程共享的,两个线程用两个不同的对象,也没有关系,不影响结果。
  b.beTested(this);
} catch (InterruptedException e) {
  e.printStackTrace();
}
  }
}

class BeTested {
  static Object obj = new Object();;
  public void beTested(Thread t) throws InterruptedException{
synchronized (obj) { // obj 为同步监视器
  System.out.println(t.getName() + "------判断所取钱数是否大于余额------");
  t.sleep(1000); // 如果没有同步这样能理明显地看到这两步骤不能在一个线程,同一个时间片里执行完成。
  System.out.println(t.getName() + "======做减法操作,取出现金======");
}
  }
}

执行结果如下:

Thread-0------判断所取钱数是否大于余额------
Thread-0======做减法操作,取出现金======
Thread-1------判断所取钱数是否大于余额------
Thread-1======做减法操作,取出现金======

注意:同步监视器对象的选用很关键。要选择线程共享的对象,比如上面例子的 obj, 它是static修饰的才行,如果没有static修饰,则是使用不同的同步监视器(不是同一个对象),相当于是两把钥匙。

  (如果obj = "aaaa" 没有static修饰也可以实现同步,那是因为这个obj引用的常量池里的同一个string对象,强烈不推荐使用)

2、同步方法(非静态方法)

把上面的那两类改成如下,main方法所在类不变。

class Thread1 extends Thread{

  static BeTested b = new BeTested(); // 在这种方法中,这里必须是同个对象(static修饰),下文会详细说明
  @Override
  public void run() {
super.run();
try {
  b.beTested(this);
} catch (InterruptedException e) {
  e.printStackTrace();
}
  }
}

class BeTested {
  static Object obj = new Object();;
  public synchronized void beTested(Thread t) throws InterruptedException{
  System.out.println(t.getName() + "------判断所取钱数是否大于余额------");
  t.sleep(1000); 
  System.out.println(t.getName() + "======做减法操作,取出现金======");
  }
}

执行结果如下:

Thread-0------判断所取钱数是否大于余额------
Thread-0======做减法操作,取出现金======
Thread-1------判断所取钱数是否大于余额------
Thread-1======做减法操作,取出现金======

注意:因为同步方法中,所用的同步监视器不能指定,默认使用的调用该方法的对象,也就是this。所以 Thread1 类中相对于示例1中同步代码块中修改的部分, 也是要static修饰。也就是说要使用同一个对象。

3、同步方法(静态方法)

把上面的那两类改成如下,main方法所在类不变。

class Thread1 extends Thread{

  @Override
  public void run() {
super.run();
try {
  BeTested b = new BeTested(); // 这里每个线程使用不同的对象。
  b.beTested(this);
} catch (InterruptedException e) {
  e.printStackTrace();
}
  }
}

class BeTested {
  static Object obj = new Object();;
  public static synchronized void beTested(Thread t) throws InterruptedException{
  System.out.println(t.getName() + "------判断所取钱数是否大于余额------");
  t.sleep(1000); 
  System.out.println(t.getName() + "======做减法操作,取出现金======");
  }
}

执行结果如下:

Thread-0------判断所取钱数是否大于余额------
Thread-0======做减法操作,取出现金======
Thread-1------判断所取钱数是否大于余额------
Thread-1======做减法操作,取出现金======

注意:因为同步静态方法中,同步监视器是这个类而不是这个类的对象。所以Thread1 类中相对于示例2中同步代码块中修改的部分,不须要用static修饰,不是同一个对象也没关系。因为这个类他本身就是共享的。

总结:如上几种方式进行线程同步处理时,要注意你所使用的同步监视器对象,它必须是共享的。

注:还有使用同步锁的方式实现线程同步,本篇文章不做讨论。

以上这篇浅谈同步监视器之同步代码块、同步方法就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • 利用Java正则表达式校验邮箱与手机号

    利用Java正则表达式校验邮箱与手机号

    利用Java正则表达式校验邮箱与手机号。需要的朋友可以过来参考下,希望对大家有所帮助
    2013-10-10
  • 一小时迅速入门Mybatis之bind与多数据源支持 Java API

    一小时迅速入门Mybatis之bind与多数据源支持 Java API

    这篇文章主要介绍了一小时迅速入门Mybatis之bind与多数据源支持 Java API,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2021-09-09
  • Java13 明天发布(最新最全新特性解读)

    Java13 明天发布(最新最全新特性解读)

    这篇文章主要介绍了Java13 明天发布,最新最全新特性解读,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2019-09-09
  • 解决IDEA中pom.xml文件变为灰色的问题

    解决IDEA中pom.xml文件变为灰色的问题

    这篇文章主要给大家介绍了如何解决IDEA中pom.xml文件变为灰色的问题,文中通过图文结合给大家介绍的非常详细,对大家的学习或工作有一定的帮助,需要的朋友可以参考下
    2023-12-12
  • 教你在一分钟之内理解Java Lambda表达式并学会使用

    教你在一分钟之内理解Java Lambda表达式并学会使用

    今天给大家带的文章是Java8新特性的相关知识,文章围绕着如何在一分钟之内理解Java Lambda表达式并学会使用展开,文中有非常详细的介绍,需要的朋友可以参考下
    2021-06-06
  • SpringBoot生成PDF的五种实现方法总结

    SpringBoot生成PDF的五种实现方法总结

    这篇文章主要介绍了SpringBoot生成PDF的五种实现方法,在开发中经常会遇到需要进行对一些数据进行动态导出PDF文件,然后让用户自己选择是否需要打印出来,这篇文章我们来介绍五种实现方法,需要的朋友可以参考下
    2024-10-10
  • Java中的PowerMock使用实践

    Java中的PowerMock使用实践

    这篇文章主要介绍了Java中的PowerMock使用实践,@PrepareForTest和@RunWith是成对出现的,一般@RunWith(PowerMockRunner.class),@PrepareForTest的值是引用的静态方法或私有方法的类,需要的朋友可以参考下
    2023-12-12
  • java算法之二分查找法的实例详解

    java算法之二分查找法的实例详解

    这篇文章主要介绍了java算法之二分查找法的实例详解的相关资料,这里提供简单实例帮助大家学习理解这部分内容,需要的朋友可以参考下
    2017-08-08
  • Java实现大数运算的实例代码

    Java实现大数运算的实例代码

    这篇文章主要介绍了Java实现大数运算的实例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-06-06
  • RabbitMQ的安装和配置可视化界面的详细步骤

    RabbitMQ的安装和配置可视化界面的详细步骤

    这篇文章主要介绍了RabbitMQ的安装和配置可视化界面的详细步骤,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-06-06

最新评论