Java多线程(三)---同步与死锁

问题的引出:

以卖火车票为例,如果现在要是想买火车票的话可以去火车站买或者去各个网络售票点,但是不管有多少个地方卖火车票,但是火车票的数量是一定的,所有的售票点一定共享同一份火车票。这里将售票点比作线程的话,也及时所有的线程共享一份进程资源。

代码的实现:

class MyThread1 implements Runnable{
	private int ticket=5;
	public void run() {
		for(int i=0;i<100;i++) {
			if(ticket>0) {
				System.out.println("卖第"+ticket--+"张票");}}}}
public class TongBu {
	public static void main(String[] args) {
		MyThread1 mThread1=new MyThread1();
		Thread t1=new Thread(mThread1);
		Thread t2=new Thread(mThread1);
		Thread t3=new Thread(mThread1);
		t1.start();
		t2.start();
		t3.start();}}

运行结果:
图片

但是在实际应用中,我们的数据都是保存在数据库中,而且我们的用户订单信息也是保存在数据库中,这也就是意味着我们将数据保存到数据库中是需要一定的时延的,这里我们模拟一下网络时延。

实现代码:

class MyThread1 implements Runnable{
	private int ticket=5;
	public void run() {
		for(int i=0;i<100;i++) {
			if(ticket>0) {
				System.out.println("卖第"+ticket--+"张票");
				try {
					Thread.sleep(500);
				} catch (Exception e) {
					// TODO: handle exception}}}}}
public class TongBu {
	public static void main(String[] args) {
		MyThread1 mThread1=new MyThread1();
		Thread t1=new Thread(mThread1);
		Thread t2=new Thread(mThread1);
		Thread t3=new Thread(mThread1);
		t1.start();
		t2.start();
		t3.start();}}

大家再看一下运行结果:
图片

可以看到票数重复售卖,程序代码出现问题,这也就会导致用户的冲突。

为什么会产生这样的现象?

这里面我们简单分析一下操作代码的实现步骤过程?
在这里插入图片描述

这里我们可以看到线程1取出资源中的数据,进行减一操作后再放回去,这里面我们重复出现数的原因是,当线程1取出资源的时候,线程2也同时取出,比如线程1取出之前是3,线程1出去后的数字是3,但是因为网络时延,线程1刚取出还没有处理放回去的时候,线程2页同时取出资源3,这时候线程1和线程2拿到的数据都是3,这时线程1计算后为2,输出2,线程2计算也为2输出2,这样就产生重复数字。

如何解决这个问题?

如果想解决这样的问题,就必须使用同步,所谓的同步就是指多个操作在同一个时间段内只能有一个线程进行,其他线程要等待此线程完成后才能继续执行,也就是当线程1在执行的时候,也就是线程1取出数据后,线程2就不能对资源变量进行操作,这就是同步加锁。

图片

同步

要想解决资源共享的同步操作问题,可以使用同步代码块及同步方法两种方式完成。

同步代码块

代码块我们一共分为四种代码块:

  • 普通代码块:是直接定义在方法之中的代码块。
  • 构造块:是直接定义在类中的,优先于构造方法执行,可以重复调用。
  • 静态快:是使用static关键字声明的,优先于构造块执行,只执行一次。
  • 同步代码块:使用synchronized关键字声明的代码块,称为同步代码块。

定义格式:

synchronize(同步对象){

需要同步加锁的代码;}

一般将当前对象作为同步的对象,使用this表示。

public void run() {
		for(int i=0;i<100;i++) {
			synchronized (this) {			
			if(ticket>0) {				
				try {
					Thread.sleep(500);
				} catch (Exception e) {
					// TODO: handle exception
				}
				System.out.println("卖第"+ticket--+"张票");
			}}

程序会一步一步执行,但是速度会降低。
同步方法

在方法返回值前面加上一个synchronize 。

格式为synchronize 方法返回值 方法名称(参数列表){}

死锁

程序过多会产生死锁

同步是产生死锁的前提,比如有两个线程,线程A执行需要线程B的资源,线程B执行同样需要线A的资源,就这样一直处于等待对方拿出资源,这就叫死锁。

多线程以及多进程改善了系统资源的利用率并提高了系统的处理能力,然而,并发执行也带来了新的问题——死锁。所谓死锁是指多个线程因竞争资源而造成的一种僵局(互相等待),若无外力作用,这些进程都将无法向前推进。

所谓死锁是指两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

下面我们以一个实例来说明死锁现象。

两个人面对面过独木桥,甲和乙都已经在桥上走了一段距离,即占用了桥的资源,甲如果想通过独木桥的话,乙必须退出桥面让出桥的资源,让甲通过,但是乙不让,乙还想先过去,这样甲乙都卡在桥上不动这就是死锁。

图片

死锁产生的必要条件:

死锁产生需要有四大条件缺一不可,也就类似于多态必须在继承的基础上产生。

  • 互斥条件:进程要求对所分配的资源(如打印机)进行排他性控制,即在一段时间内某个资源只能为一个进程所占有。此时若有其他进程请求资源,则请求的进程只能等待。
  • 不剥夺条件:进程所获得的资源在未使用完毕之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己释放(只能主动释放,不能被动释放)
  • 请求和保持条件:进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源已被其他进程占有,此时请求进程被阻塞,但对自己获得的资源不会释放。

循环等待条件:存在一种进程资源的循环等待链,链中每一个进程已获得的资源同时被链中的下一个进程请求,及存在一个处于等待状态的进程集合:p1,p2…pn},其中Pi等 待的资源被P(i+1)占有(i=0, 1, …, n-1),Pn等待的资源被P0占有,如图1所示。

直观上看,循环等待条件似乎和死锁的定义一样,其实不然。按死锁定义构成等待环所 要求的条件更严,它要求Pi等待的资源必须由P(i+1)来满足,而循环等待条件则无此限制。 例如,系统中有两台输出设备,P0占有一台,PK占有另一台,且K不属于集合{0, 1, …, n}。

  • Pn等待一台输出设备,它可以从P0获得,也可能从PK获得。因此,虽然Pn、P0和其他 一些进程形成了循环等待圈,但PK不在圈内,若PK释放了输出设备,则可打破循环等待, 如图2-16所示。因此循环等待只是死锁的必要条件。

图片

如何避免死锁?

  • 破坏死锁产生的四大条件之一
  • 加锁顺序(线程按照一定的顺序加锁)
  • 加锁时限(线程尝试获取锁的时候加上一定的时限,超过时限则放弃对该锁的请求,并释放自己占有的锁)
  • 死锁检测

猜你喜欢

转载自blog.csdn.net/qq_44762290/article/details/112379636
今日推荐