如何能取出比余额还多的钱?如何让哲学家吃上饭?Java多线程同步(Synchronization)产生问题分析及解决方法

多线程同步(Synchronization)产生问题分析及解决方法

一、如何能取出比余额还多的钱?(多线程同步产生问题)

  生活中,我们基本上都去银行取过钱,但是肯定大家都是卡里有多少钱就只能取多少钱。取出比余额还多的钱,这不是在搞笑嘛,那银行肯定都倒闭了。但是从理论上来说如果银行没加锁是有可能的。我们都知道去银行取钱有两种方式,一是存折,一是银行卡,且两者的余额共通。如果你愿意搞事情,你可以两边同时取,假设余额为200元,你两边先同时查询,两边的余额都为200,只要手速够快,在两边数据未同步前,你就能取出400元。而这就是多线程同步过程中所产生的问题。
  接下来我们来用实际多线程程序进行一下模拟:
1、重写线程中的run方法

public class quqian implements Runnable {
    static quqian money=new  quqian();
    static int i=0;//取出的钱
    public void run() {
     //取钱操作
  for( int j=0;j<10000;j++){ //取出的钱不能超过余额,余额10000
             i++;//一次取一元
     }
   }   

2、编写主方法,同时启动用银行卡取钱和用存折取钱两个线程,但是调用同一个方法,代表取同一个账号的钱

public static void main(String[] args) throws InterruptedException {
        Thread card=new Thread(money);//银行卡
        Thread book=new Thread(money);//存折
        card.start();
        book.start();
        card.join();
        book.join();
        System.out.println("一共取出的钱"+i); //最终取出的钱
    }

因为两个线程调用了同一方法,所以这时候数值会出错,运行多次。

一共取出的钱10968
一共取出的钱10501
一共取出的钱11249

我们可以发现每次的值都不相同,这是由于两个线程调用了同一个方法,使得方法中的j值出现错误。
这就是多线程并行过程中出现的一个问题。
3、为了解决这个问题,我们可以使第一个线程结束后再调用第二个线程。比如:

  public static void main(String[] args) throws InterruptedException {
        Thread card=new Thread(money);//银行卡
        Thread book=new Thread(money);//存折
        card.start();
 	card.join();
        book.start();
        book.join();
        System.out.println("一共取出的钱"+i); //最终取出的钱
    }
一共取出的钱20000

这样取出的钱就是20000,而为了方便java中提供了一个锁方法(synchronized),使得在一个线程调用该方法时,其他线程无法调用。

    //取钱操作
     synchronized( money){
      for( int j=0;j<10000;j++){ //取出的钱不能超过余额
             i++;//一次取一元
     }
   }

这样就可以使一个线程调用该方法。将synchronized作用于一个给定的实例对象money,即当前实例对象就是锁对象,每次当线程进入synchronized包裹的代码块时就会要求当前线程持有money实例对象锁,如果当前有其他线程正持有该对象锁,那么新到的线程就必须等待,这样也就保证了每次只有一个线程执行i++;操作。当然除了money作为对象外,我们还可以使用this对象(代表当前实例)或者当前类的class对象作为锁。

4、synchronized的三种应用方式
Java中每一个对象都可以作为锁,这是synchronized实现同步的基础:
(1)普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
(2)静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类对象的锁
(3)同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。

二、哲学家吃饭问题

在学习了锁后,我们需要考虑一下著名的哲学家吃饭问题。
  哲学家就餐问题是在计算机科学中的一个经典问题,用来演示在并行计算中多线程同步(Synchronization)时产生的问题。在给类或方法加锁后,就会出现该问题。
问题描述:有五个哲学家,他们的生活方式是交替地进行思考和进餐。他们共用一张圆桌,分别坐在五张椅子上。在圆桌上有五个碗和五支筷子,平时一个哲学家进行思考,饥饿时便试图取用其左、右最靠近他的筷子,只有在他拿到两支筷子时才能进餐。进餐完毕,放下筷子又继续思考。
约束条件:(1)只有拿到两只筷子时,哲学家才能吃饭。(2)如果筷子已被别人拿走,则必须等别人吃完之后才能拿到筷子。(3)任一哲学家在自己未拿到两只筷子吃饭前,不会放下手中拿到的筷子。
  在实际的计算机问题中,缺乏筷子可以类比为缺乏共享资源。一种常用的计算机技术是资源加锁,用来保证在某个时刻,资源只能被一个程序或一段代码访问。当一个程序想要使用的资源已经被另一个程序锁定,它就等待资源解锁。当多个程序涉及到加锁的资源时,在某些情况下就有可能发生死锁。例如,某个程序需要访问两个文件,当两个这样的程序各锁了一个文件,那它们都在等待对方解锁另一个文件,而这永远不会发生。

三、怎样让他们吃上饭(问题解法)

1、服务生解法

一个简单的解法是引入一个餐厅服务生,哲学家必须经过他的允许才能拿起餐叉。因为服务生知道哪只餐叉正在使用,所以他能够作出判断避免死锁。
为了演示这种解法,假设哲学家依次标号为A至E。如果A和C在吃东西,则有四只餐叉在使用中。B坐在A和C之间,所以两只餐叉都无法使用,而D和E之间有一只空余的餐叉。假设这时D想要吃东西。如果他拿起了第五只餐叉,就有可能发生死锁。相反,如果他征求服务生同意,服务生会让他等待。这样,我们就能保证下次当两把餐叉空余出来时,一定有一位哲学家可以成功的得到一对餐叉,从而避免了死锁。

2资源分级解法

另一个简单的解法是为资源(这里是餐叉)分配一个偏序或者分级的关系,并约定所有资源都按照这种顺序获取,按相反顺序释放,而且保证不会有两个无关资源同时被同一项工作所需要。在哲学家就餐问题中,资源(餐叉)按照某种规则编号为1至5,每一个工作单元(哲学家)总是先拿起左右两边编号较低的餐叉,再拿编号较高的。用完餐叉后,他总是先放下编号较高的餐叉,再放下编号较低的。在这种情况下,当四位哲学家同时拿起他们手边编号较低的餐叉时,只有编号最高的餐叉留在桌上,从而第五位哲学家就不能使用任何一只餐叉了。而且,只有一位哲学家能使用最高编号的餐叉,所以他能使用两只餐叉用餐。当他吃完后,他会先放下编号最高的餐叉,再放下编号较低的餐叉,从而让另一位哲学家拿起后边的这只开始吃东西。
尽管资源分级能避免死锁,但这种策略并不总是实用的,特别是当所需资源的列表并不是事先知道的时候。例如,假设一个工作单元拿着资源3和5,并决定需要资源2,则必须先要释放5,之后释放3,才能得到2,之后必须重新按顺序获取3和5。对需要访问大量数据库记录的计算机程序来说,如果需要先释放高编号的记录才能访问新的记录,那么运行效率就不会高,因此这种方法在这里并不实用。
这种方法经常是实际计算机科学问题中最实用的解法,通过为分级锁指定常量,强制获得锁的顺序,就可以解决这个问题。

Chandy/Misra解法

1984年,K. Mani Chandy和J. Misra提出了哲学家就餐问题的另一个解法,允许任意的用户(编号P1, …, Pn)争用任意数量的资源。与迪科斯彻的解法不同的是,这里编号可以是任意的。
1.对每一对竞争一个资源的哲学家,新拿一个餐叉,给编号较低的哲学家。每只餐叉都是“干净的”或者“脏的”。最初,所有的餐叉都是脏的。
2.当一位哲学家要使用资源(也就是要吃东西)时,他必须从与他竞争的邻居那里得到。对每只他当前没有的餐叉,他都发送一个请求。
3.当拥有餐叉的哲学家收到请求时,如果餐叉是干净的,那么他继续留着,否则就擦干净并交出餐叉。
4.当某个哲学家吃东西后,他的餐叉就变脏了。如果另一个哲学家之前请求过其中的餐叉,那他就擦干净并交出餐叉。
这个解法允许很大的并行性,适用于任意大多问题。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值