JAVA-初步认识-第十三章-同步函数
一.
现在用一个示例,将刚才所讲的同步内容练习一下。
对于一个函数中,存在多个线程,如果出现安全隐患,这隐患是在哪儿?以及如何通过同步函数来解决。(相当于一个实操)
存一百,存三次是共同的,两个人执行的是同样的动作。
线程开启之前,要先有任务。这个任务要么叫爹覆盖我的方法,要么实现Runable把你任务封装对象给传递过来。两种不同的操作方式,都是为了将需要执行的代码和线程的任务联系起来。
----------
对于书写的程序后面怎么运行,运行过程中涉及哪些问题,问题怎么处理的我都不是很关心。我关心的是如果将需求变成该程序的,这才是核心。
需求是:储户,两个,到银行存钱。每次存100,存三次。这里面的名词有三个,储户,银行,钱。首先可以明确的是,储户是可以单独成一类的,存钱的动作是要封装成任务对象。因为是两个储户在存钱,这是个多线程执行的程序。我觉着,存钱这个动作是钱自己知道,定义的类应该是钱的描述类,而不是银行的描述类。最后一个类是包含主函数的执行类。
----------
编译运行的结果如上图所示,sum是银行的金库。这里确实是开启了两个线程,而且线程之间是交替运行了。
这两个储户实际上是去了两个银行,而不是在一个银行的多个柜台。因为Bank b的创建是在run方法中,这时要修改程序,
这次的结果符合银行金库的数目跳动规则,在储户存钱时,金库数目始终是上涨的,打印结果有点乱是正常的。
现在就问,这个程序有没有安全隐患,有的话,怎么解决?
要想知道程序的安全隐患在哪儿?必须要知道刚才讲述的造成程序安全隐患的原因。那个原因里面涉及了几个要素。在线程运行的代码当中,是否有共享数据,这里的b就是共享数据。b.add(100),b调用add方法就一句,没事儿。首先,要搞清楚线程的代码有哪些?
正因为b的调用,导致add()方法也变成了run方法中的内容。run方法进栈后,add方法也进栈了,进的是同一个线程栈。add代码本身也属于线程的代码,因为它被我们所定义的线程运行到了。add代码运行的时候,sum就是共享数据。
对于第一点满足了,存在共享数据。第二个就是数据的代码操作不止一条啊。一条加法,一条输出,至少有两条。
有人说,一个加法,一个输出有问题吗?这就很难说了,
有一个线程进来了,num=100, sum=0+100=100,突然冻结了。这时又进来一个线程,num也是100,sum=100+100,输出了sum为200。然后接着执行第一条线程,输出的也是200。这就出现问题了,本来第一条线程应该输出100的。这就是安全隐患。
为了让安全隐患显现出来,修改一下程序。(使用sleep方法会出现异常,为了程序继续执行,采用try-catch处理)
DOS结果显示确实出现了安全隐患,为了解决这个问题,我们要对程序进行封装。
这时要考虑封装谁,安全问题出现在sum=sum+num,和输出语句之间。不能直接将所有语句放入同步函数中,这样是有问题的,一会儿演示。
有的人为了图省事,直接在括号里创建匿名对象,这也是错误的,因为这就不是共享对象了,而是每个线程一个对象了。这和刚才将创建对象放置在run方法中是一回事。这就成了每个人都有一个单独地锁么。
增加同步函数后,安全问题解决了,
这是第一步,我们拿着一个程序,试着找出线程安全隐患是在哪儿发生的。紧跟着又是如何去解决的。
现在开始讲述另外一部分,原先出问题的代码都属于add函数,而且函数本身就是一种封装体,同步代码块本身也是封装体。其实它俩都是封装体,哪儿不一样?一个是封装,一个是带有特性的封装,就是同步特效。那么,能不能借助原有函数辅以同步特效,实现解决安全隐患的作用。怎么做?将同步关键字作为函数的修饰符,这样就具备了同步的修饰功能,
编译运行也没有任何问题。改进后的方式比较简单,这个称之为同步函数。这是同步的第二种表现形式,同步代码块是第一种。
同步函数是第二种,都能实现同步的效果,解决线程的安全问题。
由于同步函数的出现,obj对象就没有用了,将其注释掉。原先同步代码块中,对象是锁,现在同步函数中,锁在哪儿?