多线程模拟售票及线程同步与死锁

原创 2013年12月04日 22:59:02

1.线程同步
写一个模拟现实售票的例子。
首先,定义一个售票类Tickets。
//售票类
class Tickets extends Thread {
	
	//定义总票数为100,因为多个窗口售票,所以定义为静态的,即类成员共享
	private static int tickets = 100;
	
	public Tickets(String name) {
		super(name);
	}
	
	//重写run方法,封装运行代码
	public void run() {
		//循环售票
		while(true) {
			//如果票数大于0,则可以继续出售。每售出一张,票数减一
			if(tickets > 0) {
				System.out.println(Thread.currentThread().getName() + "售出第" + (tickets--) + "号票");
			}
		}
	}	
}
接下来,开启四个线程模拟四个售票窗口
public class TicketDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		//创建四个售票窗口
		Tickets t1 = new Tickets("1号窗口");
		Tickets t2 = new Tickets("2号窗口");
		Tickets t3 = new Tickets("3号窗口");
		Tickets t4 = new Tickets("4号窗口");
		//开始售票
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}
运行程序,可以看到四个窗口开始售票,直到票售完为止。



但是,这个程序有没有安全隐患呢?
我们把售票类的循环体内代码稍微改动一下。
//售票类
class Tickets extends Thread {
	
	//定义总票数为100,因为多个窗口售票,所以定义为静态的,即类成员共享
	private static int tickets = 100;
	
	public Tickets(String name) {
		super(name);
	}
	
	//重写run方法,封装运行代码
	public void run() {
		//循环售票
		while(true) {
			//如果票数大于0,则可以继续出售。每售出一张,票数减一
			if(tickets > 0) {
				//让线程执行到此,休眠10毫秒
				//多线程操作时,会出现安全问题,可能出现0,-1,-2号票
				try{
					Thread.sleep(10);
				}catch(InterruptedException e){
					e.printStackTrace();
				}
				
				System.out.println(Thread.currentThread().getName() + "售出第" + (tickets--) + "号票");
			}
		}
	}	
}
再次运行程序,会出现如下运行结果:

我们看到,售票售出了0、-1、-2号票,当然,每次运行的结果可能不同,但是可能会出现与现实情况不符的现象。

这是为什么呢?

这就是多线程同步的安全问题。出现问题的原因是:多个线程(4个窗口)操作同一共享数据(票)时,一条线程只执行了一部分,还没执行完,而另一条线程参与进来执行,导致共享数据错误。就如上例所示,假使票卖到只剩1张时,一个窗口将要出售最后一张票。当其判断票数大于0成立后,还没执行下一句,另一条线程又抢夺到cpu的执行权,同样判断票数大于0成立。于是,下面无论这两条线程哪一条抢夺到cpu的执行权,这两条线程都会先后执行,就会出现票为-1的现象。

如何解决多线程安全问题呢?

对多条线程操作共享数据时,只能让一个线程全都执行完。在执行过程中,其他线程不可以参与执行。
Java对于多线程安全问题,提供了专业的解决方式:使用同步代码块。

synchronized(对象)
{
          要被同步的代码;
}

对象如同锁,持有锁的线程可以在同步中执行。没有锁的线程,即便获取了cpu的执行权,也无法执行。

我们把售票类的代码加上同步代码块
//售票类
class Tickets extends Thread {
	
	//定义总票数为100,因为多个窗口售票,所以定义为静态的,即类成员共享
	private static int tickets = 100;
	
	public Tickets(String name) {
		super(name);
	}
	
	//重写run方法,封装运行代码
	public void run() {
		//循环售票
		while(true) {
			//同步代码块,使用本类字节码对象作为锁
			synchronized(Tickets.class) {
				//如果票数大于0,则可以继续出售。每售出一张,票数减一
				if(tickets > 0) {
					//让线程执行到此,休眠10毫秒
					//多线程操作时,会出现安全问题,可能出现0,-1,-2号票
					try{
						Thread.sleep(10);
					}catch(InterruptedException e){
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName() + "售出第" + (tickets--) + "号票");
				}
			}
		}
	}	
}
此时再次运行程序,将会看到不会在出现0、-1、-2号票。



最终,我们去掉使线程休眠的代码,完成售票类的最终代码。
//售票类
class Tickets extends Thread {
	
	//定义总票数为100,因为多个窗口售票,所以定义为静态的,即类成员共享
	private static int tickets = 100;
	
	public Tickets(String name) {
		super(name);
	}
	
	//重写run方法,封装运行代码
	public void run() {
		//循环售票
		while(true) {
			//同步代码块,使用本类字节码对象作为锁
			synchronized(Tickets.class) {
				//如果票数大于0,则可以继续出售。每售出一张,票数减一
				if(tickets > 0) {
					System.out.println(Thread.currentThread().getName() + "售出第" + (tickets--) + "号票");
				}
			}
		}
	}	
}

注:能不能将while循环放在同步代码块中呢?
不能!否则会出现死循环!因为拿到锁的线程会一直将同步代码块都执行完才会释放锁,而while(true)会一直循环下去。

什么时候使用同步呢?
同步的前提:
1)必须要有两个或两个以上的线程
2)必须是多个线程使用同一个锁

如何判断哪些代码要加同步呢?
1)明确哪些代码是多线程运行代码
2)明确共享数据
3)明确多线程运行代码中哪些语句操作共享数据


2.死锁
使用线程同步,有时会出现死锁的情况。看下面程序:
package com.itheima;

/**
 * 死锁程序
 * @author YP
 * */

public class DeadLockDemo {

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		//开启两个线程
		new Thread(new DeadLock(true)).start();
		new Thread(new DeadLock(false)).start();
	}

}

//提供锁的类
class MyLock
{
	//为了方便运用锁,将锁定义为静态的
	static Object locka = new Object();
	static Object lockb = new Object();
}

//实现implements接口创建线程类
class DeadLock implements Runnable {
	
	//判断执行何种语句的标志
	private boolean flag;
	
	DeadLock(boolean flag){
		this.flag = flag;
	}
	
	public void run() {
		if(flag){
			//先要获得a锁才可执行下面语句
			synchronized(MyLock.locka){
				System.out.println("if locka");
				//要继续执行下面语句,必须再获取b锁
				synchronized(MyLock.lockb) {
					System.out.println("if lockb");
				}
			}
		}else{
			//先要获得b锁才可执行下面语句
			synchronized(MyLock.lockb){
				System.out.println("else lockb");
				//要继续执行下面语句,必须再获取a锁
				synchronized(MyLock.locka) {
					System.out.println("else locka");
				}
			}
		}
	}
}
运行程序:

我们看到,出现了死锁。

死锁是如何出现的呢?
当一个线程拥有A锁,还要去拿B锁才可执行;而另一个线程拥有B锁,还要去拿A锁才可执行。两个线程互相僵持,致使死锁情况出现。
所以,我们写代码时要避免死锁情况的出现。给出如下建议:

1、在程序中尽量使用开放调用。依赖于开放调用的程序,相比于那些在持有锁的时候还调用外部方法的程序,更容易进行死锁自由度的分析。重新构建synchronized使开放调用更加安全。所谓开放调用是指调用的方法本身没有加锁,但是要以对方法操作的内容进行加锁。

2、如果你必须获得多个锁,那么锁的顺序必须是你设计工作的一部分:尽量减少潜在锁之间的交互数量,遵守并文档化该锁顺序协议。监测代码中死锁自由度的策略有:

1)识别什么地方会获取多个锁,并使锁数量尽可能少,保证它们的顺序在程序中一致。

2)在没有非开放调用的程序中,发现那些获得多重锁的实例是非常简单的。

3、尝试定时的锁,使用每个显式Lock类中定时tryLock特性,来替代使用内部锁机制。


版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

Java多线程之线程同步和死锁

Java多线程之线程同步和死锁 

Java多线程 线程同步与死锁

1.线程同步多线程引发的安全问题一个非常经典的案例,银行取钱的问题。假如你有一张银行卡,里面有5000块钱,然后你去银行取款2000块钱。正在你取钱的时候,取款机正要从你的5000余额中减去2000的...

Java多线程-(8)线程同步、锁、死锁

一、线程同步、锁 至于怎么用或者原理就不做太多讲述了,只总结了下要注意的点: 1、Java中每个对象都有一个内置锁;当提到同步时,应该清楚在什么上同步?也就是说,在哪个对象上同步? 2、非静...

JAVA学习笔记(2)_____线程同步锁(synchronized)模拟火车售票窗口

java中cpu分给每个线程的时间片是随机的并且在java中好多都是多个线程共用一个资源,比如火车卖票,火车票是一定的,但卖火车票的窗口到处都有,每个窗口就相当于一个线程,这么多的线程共用所有的火车票...

Java第七课 Java的多线程程序进程和线程的概念,实现多线程的两种方式,线程同步的原理,线程的死锁,运用wait和notify来实现producer - consumer关系,线程终止的两种情况。

程序、进程和线程 1.程序是计算机指令的集合,它以文件的形式存储在磁盘上。 2.进程:是一个程序在其自身的地址空间中的一次执行活动。 3.进程是资源申请、调度和独立运行的单位,因此,它使...

多线程--线程同步、死锁、守护线程、多线程下载

1 线程同步 当两个或多个线程需要访问同一资源时,需要确保该资源某一时刻只能被一个线程使用 1.1同步代码块 同步代码块:synchronized放在对象前面限制一段代码的执行 synchronize...

心得3--由售票系统中的抢票机制解说同步线程及死锁案例分析

一.  这里跟大家分享一下售票系统,这里是一个抢票系统,讨论一下线程同步的作用(关键字synchronized),第一种是正确的程序,下面两种分别是两种不同的错误。 1.运用synchronize...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)