电影院卖票-线程
需求
有100张票,有三个窗口卖票,设计一个程序模拟电影院卖票
思路
1.定义一个类SellTicket,实现Runnable接口,里面定义一个成员变量:private int tickets=100
2.在SellTicket类中重写run()方法实现卖票:判断票数大于0,就卖票,告知是哪一个窗口;卖票后,总票数-1;用死循环让卖票的动作一直执行
3.定义一个测试类SellTicketDemo,里面有main方法:创建SellTicket类的对象;创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,病给出对应的窗口的名称;启动线程
//SellTicketDemo.java
/*
* #### 需求
有100张票,有三个窗口卖票,设计一个程序模拟电影院卖票
#### 思路
1.定义一个类SellTicket,实现Runnable接口,里面定义一个成员变量:
private int tickets=100
2.在SellTicket类中重写run()方法实现卖票:判断票数大于0,就卖票,告知是哪一个窗口;
卖票后,总票数-1;用死循环让卖票的动作一直执行
3.定义一个测试类SellTicketDemo,里面有main方法:创建SellTicket类的对象;
创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口的名称;
启动线程
*/
public class SellTicketDemo {
public static void main(String[] args) {
// 创建SellTicket类的对象
SellTicket st = new SellTicket();
// 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口的名称'
Thread t1 = new Thread(st,"窗口1");
Thread t2 = new Thread(st,"窗口2");
Thread t3 = new Thread(st,"窗口3");
// 启动线程
t1.start();
t2.start();
t3.start();
}
}
//SellTicket.java
//定义一个类SellTicket,实现Runnable接口,里面定义一个成员变量:
//private int tickets=100
public class SellTicket implements Runnable{
private int tickets = 100;
public void run() {
// 判断票数大于0,就卖票,告知是哪一个窗口;
// 卖票后,总票数-1;
// 用死循环让卖票的动作一直执行
while(true) {
if(tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
}
}
问题1:卖票间隔一秒
public void run() {
// 判断票数大于0,就卖票,告知是哪一个窗口;
// 卖票后,总票数-1;
// 用死循环让卖票的动作一直执行
while(true) {
if(tickets > 0) {
//通过sleep()方法模拟出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
}
}
问题2:一张票卖出三次
public void run() {
// 判断票数大于0,就卖票,告知是哪一个窗口;
// 卖票后,总票数-1;
// 用死循环让卖票的动作一直执行
//相同的票出现了多次
while(true) {
//tickets=100,t1,t2,t3
//t1抢到cpu执行权
if(tickets > 0) {
//通过sleep()方法模拟出票时间
try {
//t1休息
//t2抢到cpu执行权,t2休息
//t3抢到cpu执行权,t3休息
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//假设线程按照顺序苏醒,t1抢到cpu执行权,在控制台输出100
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
//t2抢到cpu的执行权,在控制台输出100
//t3抢到cpu的执行权,在控制台输出100
tickets--;
//如果三个线程按顺序执行,执行三次--操作,票就变成了97
}
}
}
问题3:出现票为-1的情况
//假设线程按照顺序苏醒,t1抢到cpu执行权,在控制台输出1
//假设t1继续抢到cpu执行权,在tickets-- = 0
//假设t2抢到cpu执行权,在控制台输出0
//假设t1继续抢到cpu执行权,在tickets-- = -1
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
问题原因:线程执行的随机性
数据安全问题的解决
是否是多线程环境
是否有共享数据
是否有多条语句操作共享数据
解决
引用锁机制:同步代码块实现
synchronized(对象){
多条语句操作共享数据的代码;
}
//定义锁对象
private Object obj = new Object();
public void run() {
// 判断票数大于0,就卖票,告知是哪一个窗口;
// 卖票后,总票数-1;
// 用死循环让卖票的动作一直执行
//相同的票出现了多次
while(true) {
synchronized(obj) {
//t1进来后,把这段代码锁上
if(tickets > 0) {
//通过sleep()方法模拟出票时间
try {
//t1休息
//t2抢到cpu执行权,t2休息
//t3抢到cpu执行权,t3休息
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//假设线程按照顺序苏醒,t1抢到cpu执行权,在控制台输出1
//假设t1继续抢到cpu执行权,在tickets-- = 0
//假设t2抢到cpu执行权,在控制台输出0
//假设t1继续抢到cpu执行权,在tickets-- = -1
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
//t1出来后,这段代码就被释放
}
}
}
当线程很多时,因为每个线程都会判断同步上的锁,耗费资源,会降低程序的运行效率
同步方法
把synchronized关键字加到方法上
同步方法的锁对象是this
public void run() {
// 判断票数大于0,就卖票,告知是哪一个窗口;
// 卖票后,总票数-1;
// 用死循环让卖票的动作一直执行
//相同的票出现了多次
while(true) {
if(x %2 ==0) {
synchronized(obj) {
//t1进来后,把这段代码锁上
if(tickets > 0) {
//通过sleep()方法模拟出票时间
try {
//t1休息
//t2抢到cpu执行权,t2休息
//t3抢到cpu执行权,t3休息
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//假设线程按照顺序苏醒,t1抢到cpu执行权,在控制台输出1
//假设t1继续抢到cpu执行权,在tickets-- = 0
//假设t2抢到cpu执行权,在控制台输出0
//假设t1继续抢到cpu执行权,在tickets-- = -1
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
//t1出来后,这段代码就被释放
}
}else {
sellTicket();
}
}
}
public synchronized void sellTicket() {
synchronized(obj) {
//t1进来后,把这段代码锁上
if(tickets > 0) {
//通过sleep()方法模拟出票时间
try {
//t1休息
//t2抢到cpu执行权,t2休息
//t3抢到cpu执行权,t3休息
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//假设线程按照顺序苏醒,t1抢到cpu执行权,在控制台输出1
//假设t1继续抢到cpu执行权,在tickets-- = 0
//假设t2抢到cpu执行权,在控制台输出0
//假设t1继续抢到cpu执行权,在tickets-- = -1
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
//t1出来后,这段代码就被释放
}
}
this:非静态方法锁对象
同步静态方法
把synchronized关键字加到静态方法上
static synchronized 返回值类型 方法名(方法参数)
SellTicket.class:静态方法的同步锁对象(类名.class)
synchronized(SellTicket.class) {
线程安全的类
StringBuffer
线程安全,可变的字符序列,JDK5开始,被StringBuilder类替代,更快但不执行同步
Vector
被同步
如果不使用同步,使用ArrayList代替
Hashtable
被同步
不需要线程同步,使用HashMap代替
Lock锁
提供比使用synchroniezd方法和语句可以获得更广泛的锁操作
void lock():获得锁
void unlock():释放锁
是接口,不能直接实例化,采用它的实现类ReentranLock实例化
ReentranLock():创建ReentranLock实例
public class SellTicket implements Runnable{
private int tickets = 100;
private int x = 0;
private Lock lock = new ReentrantLock();
public void run() {
// 判断票数大于0,就卖票,告知是哪一个窗口;
// 卖票后,总票数-1;
// 用死循环让卖票的动作一直执行
//相同的票出现了多次
while(true) {
if(x %2 ==0) {
try {
lock.lock();
//t1进来后,把这段代码锁上
if(tickets > 0) {
//通过sleep()方法模拟出票时间
try {
//t1休息
//t2抢到cpu执行权,t2休息
//t3抢到cpu执行权,t3休息
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//假设线程按照顺序苏醒,t1抢到cpu执行权,在控制台输出1
//假设t1继续抢到cpu执行权,在tickets-- = 0
//假设t2抢到cpu执行权,在控制台输出0
//假设t1继续抢到cpu执行权,在tickets-- = -1
System.out.println(Thread.currentThread().getName() + "正在出售第" + tickets + "张票");
tickets--;
}
//t1出来后,这段代码就被释放
}finally {
lock.unlock();
}
}
}
}
}