首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

自学Java第33天:用一个案例向你说明什么是线程同步?

今天是我自学Java的第33天。

感谢你的观看,谢谢你。

话不多说,开始今天的学习:线程同步。

想必很多小伙伴应该都经历过去火车站买票的情况。

现有一个案例:火车站有3个售票窗口,一共有100张票要卖,3个窗口同时卖。

对于这种情形,如何使用Java代码来实现?

一、多窗口卖票案例

根据我们这几天的学习,很显然要创建三个线程来解决这种情况,我们选择使用实现Runnable接口的这种方式来创建线程:

创建一个类MyRunnable,实现Runnable接口。

有100张票要售卖。

也就是说类中有一个成员变量是100。

重写run方法。

创建一个循环语句:

因为需要一直卖票,直到票被卖完为止,所以使用循环语句,每循环一次卖一张票,打印卖票信息并且将ticket减一。

创建MyRunnable对象。

创建三个线程。

将MyRunnable对象初始化赋值给它,并且给各个窗口命名。

启动线程。

根据我们前几天学习的线程,我们可以写出这样的代码来实现卖票的需求。

现在看看打印结果:

咦,发现结果怎么和我想象的不一样?

票确实是在卖票,但怎么会是无序的呢?并且有时还会出现重复票。

这是为什么?

因为Java虚拟机的抢占式调度,我窗口壹先进来了,但是我还没有执行完,就切换到窗口贰了

run方法中的while循环语句执行也是需要时间的,虽然它执行起来很快,需要的时间也很少,但是线程切换更加地快。

于是就会出现:窗口贰还在打印第6张票,窗口叁连续卖了好几张都卖完了这种情况。

当我们使用多个线程访问同一资源的时候,且多个线程对该资源都有操作,就容易出现线程安全问题。

什么叫线程安全问题

通俗点理解就是:线程一在执行任务的时候,还没有执行完,线程二也进来执行线程一还没执行完的任务,这就乱套了呀,这种情况就是线程不安全。

要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与票无序的问题,Java中提供了同步机制(synchronized)来解决

二、同步代码块

用synchronized这个关键字来修饰代码块:

Object是Java里的顶层父类,它代表了任意对象。

同步代码块:锁住了lock这个对象,里面的代码就只允许执行一个线程的代码。

什么意思呢?

就是现在进来了一个线程,只要你进入了我锁住的这块代码,你就必须得将我锁住的这块代码执行完。在执行完之前,别的线程根本就进不来。

其中lock可以是任意对象,但要保证它的唯一性。

这又是什么意思呢?

lock就好比是一把锁,线程一、线程二,无论哪个线程进来,面对的lock都是同一把锁,一次就只能进入一个。

如果我们将里面创建Object对象的代码放入到run方法里面,这样就会出现一个问题:

线程启动就会执行run方法,这样的话启动三个线程就会执行三次run方法,如果在run方法里面,就会new三个Object对象,也就是三个不同的lock,这样的话就相当于有三把不同的锁,还是会乱序。

这就是lock要保证它的唯一性的原因

我们再看看控制台输出情况:

票的打印确实是有序的了,也没有重复卖票。

但是现在问题又来了:第0张票和第-1张票怎么来的?

为什么会出现这种情况?

我们仔细看看while循环的代码:

现在窗口壹在售卖第1张票,卖完之后票数ticket等于0了。

但是窗口贰和窗口叁它们两个线程在干嘛?

它们早就已经进入while循环了,只不过因为先前synchronized锁住的代码块,窗口壹在里面,它们没法进去,只能等在synchronized外面,但是它们已经在while循环里面。

我们仔细分析下这个流程:

(1)窗口壹打印完第1张票,ticket变成了0,通过while循环的判断语句(ticket>0),窗口壹无法进入while循环了。

(2)窗口贰在while循环里面等着,看到窗口壹出来了立马就抢先进去了,这个时候ticket已经为0了,所以它打印第0张票,于是ticket变成了-1,窗口贰循环结束出来了,通过while循环的判断语句(ticket>0),窗口贰也无法进入while循环了。

(3)窗口叁在while循环里面等着,看到刚才抢先自己一步进入的窗口贰出来了,自己终于可以进去了,这个时候ticket已经为-1了,所以它打印第-1张票,于是ticket变成了-2,窗口叁循环结束出来了,通过while循环的判断语句(ticket>0),窗口叁也无法进入while循环了。

以上就是第0张票和第-1张票的由来

除了这个问题还有一个问题:窗口壹会一直售卖好多张票。我们如何让窗口壹卖第一张,窗口贰卖第二张,窗口叁卖第三张,窗口壹卖第四张……这样一直循环依次卖票?

面对这两个问题,我们将代码进一步优化:

加一个判断语句:如果票数小于等于0,就直接结束循环,不执行后面的语句了。

所以当窗口壹打印完第1张票,ticket变成了0。这时就算窗口贰、窗口叁这两个线程进入了synchronized里面,也会有一个if判断语句中的break直接将循环结束掉。

让该线程睡眠10毫秒:

Thread有一个静态方法sleep(),sleep是睡眠的意思,也就是说窗口壹执行完语句后,会让它睡眠10毫秒,这样的话窗口贰就能进去执行,不然的话根据Java虚拟机的抢占式调度,下一次执行语句的可能还是窗口壹。

以上就是对同步代码块的说明,除了同步代码块,还有同步方法和Lock锁也可以实现同样的功能。

三、同步方法和Lock锁

1.同步方法

同步代码块里面的代码,我们可以将其提取成一个方法,而用synchronized这个关键字来修饰的方法就叫做同步方法:

线程进来遇到同步方法后,就只能进去一个线程,其它线程得等这个线程执行完后才能进去。

同步方法:格式就是在方法声明上加上synchronized这个关键字。

如果ticket大于0就打印输出。

这个同步方法的作用和同步代码块是一样的。

2.Lock锁

lock是一个接口,它提供了比同步代码块和同步方法更广泛的锁定操作,更加地强大和体现了面向对象。

Lock锁也称同步锁,加锁与释放锁方法化了。

Lock是一个接口,无法实例化创建对象,需要其实现类创建对象。

lock方法:顾名思义就是上锁,也就是说上锁后的空间线程只能进去一个,其他线程就不去。

unlock方法:顾名思义就是解锁,将锁解开了,其他线程也就能进去了。

这用我们现实里的一个例子来理解就是:

就相当于我们去上厕所,将门给锁上,这样其他人就进不来了。

就相当于我们上完厕所,将门给解开,这样其他人就能进去了。

一个人就相当于是一个线程。以上就是同步方法和Lock锁,它们和同步代码块的作用其实是大同小异的。

总结:

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20200517A076LB00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券