Java基础_通过模拟售票情景解决线程不安全问题
用代码来模拟铁路售票系统,实现通过四个售票点发售某日某次列车的100张车票,一个售票点用一个线程表示
第一种方法:通过继承Thread类的方法创建线程

package com.Gary1;
public class TicketThread extends Thread{
//设置有100张票
private static int count = 100;
public TicketThread(String name) {
super(name);
}
@Override
public void run() {
while(true) {
//当票大于0张的时候卖票
if(count>0) {
System.out.println(getName() + "卖出第" + count + "卖票");
count--;
}else {
break;
}
}
}
}
TicketThread.java
package com.Gary1;
public class GaryTest {
public static void main(String[] args) {
TicketThread t1 = new TicketThread("售票点1");
TicketThread t2 = new TicketThread("售票点2");
TicketThread t3 = new TicketThread("售票点3");
TicketThread t4 = new TicketThread("售票点4");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
GaryTest.java
可以看出,当把count设置成static之后,线程同步时还是会造成count票的数量不安全
第二种方法:通过实现Runnable接口(实时共享数据,不需将count设置成静态)

package com.Gary1;
public class TicketRunnable implements Runnable{
//设置有100张票 count不需要设置成static
private int count = 100;
public void run() {
while(true) {
//当票大于0张的时候卖票
if(count>0) {
System.out.println(Thread.currentThread().getName() + "卖出第" + count + "卖票");
count--;
}else {
break;
}
}
}
}
TicketRunnable.java
package com.Gary1;
public class GaryTest {
public static void main(String[] args) {
TicketRunnable t = new TicketRunnable();
Thread t1 = new Thread(t,"售票点1");
Thread t2 = new Thread(t,"售票点1");
Thread t3 = new Thread(t,"售票点1");
Thread t4 = new Thread(t,"售票点1");
t1.start();
t2.start();
t3.start();
t4.start();
}
}
GaryTest.java
发现此时不管用继承Thread类或者实现Runnable接口,都无法保证线程中变量的安全!!!
(多个线程同时要修改一个变量的时候,引起冲突)
解决线程不安全方法
a)线程安全问题解决
synchronized(对象){}//锁住某个对象,如果这个对象已经被锁定,那么等待。
public void run() {
while(true) {
synchronized(lock) {//第一个线程来的时候会锁上,并拿走钥匙,第二个线程来的时候,发现被锁上,等待
//当票大于0张的时候卖票
if(count>0) {
System.out.println(Thread.currentThread().getName() + "卖出第" + count + "卖票");
count--;
}else {
break;
}
}//执行完,归还钥匙
}
}
package com.Gary1;
public class TicketRunnable implements Runnable{
//设置有100张票 count不需要设置成static
private int count = 100;
private Object lock = new Object();
public void run() {
while(true) {
synchronized(lock) {//第一个线程来的时候会锁上,并拿走钥匙,第二个线程来的时候,发现被锁上,等待
//当票大于0张的时候卖票
if(count>0) {
System.out.println(Thread.currentThread().getName() + "卖出第" + count + "卖票");
count--;
}else {
break;
}
}//执行完,归还钥匙
}
}
}
TicketRunnable.java
通过继承Thread类解决线程不安全方法
package com.Gary1;
public class TicketThread extends Thread{
//设置有100张票
private static int count = 100;
private static Object lock = new Object();
public TicketThread(String name) {
super(name);
}
@Override
public void run() {
synchronized(lock){
while(true) {
//当票大于0张的时候卖票
if(count>0) {
System.out.println(getName() + "卖出第" + count + "卖票");
count--;
}else {
break;
}
}
}
}
}
TicketThread.java
优化,使用Thread.sleep()解决抢占式优先问题,避免同一个售票点一直抢占着线程的CPU
package com.Gary1;
public class TicketRunnable implements Runnable{
//设置有100张票 count不需要设置成static
private int count = 100;
private Object lock = new Object();
public void run() {
while(true) {
synchronized(lock) {//第一个线程来的时候会锁上,并拿走钥匙,第二个线程来的时候,发现被锁上,等待
//当票大于0张的时候卖票
if(count>0) {
System.out.println(Thread.currentThread().getName() + "卖出第" + count + "卖票");
count--;
}else {
break;
}
}//执行完,归还钥匙
try {
Thread.sleep(100);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
TicketRunnable.java
b)出现线程安全问题的地方,要锁同一个对象(可以是当前对象,也可以单独创建一个对象)
c)锁住某个对象,如果这个对象已经被锁定,那么停止当前线程的执行,一直等待,一直等到对象被解锁。
(保证同一个时间,只有一个线程在使用这个对象,)
d)创建同步方法
同步方法锁的是哪个对象呢?锁定的是当前对象this
public synchronized void sellTicket() {
//synchronized(lock) {//第一个线程来的时候会锁上,并拿走钥匙,第二个线程来的时候,发现被锁上,等待
//当票大于0张的时候卖票
if(count>0) {
System.out.println(Thread.currentThread().getName() + "卖出第" + count + "卖票");
count--;
}
}//执行完,归还钥匙
package com.Gary1;
public class TicketRunnable implements Runnable{
//设置有100张票 count不需要设置成static
private int count = 100;
private Object lock = new Object();
public void run() {
while(count>0) {
sellTicket();
try {
Thread.sleep(100);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void sellTicket() {
//synchronized(lock) {//第一个线程来的时候会锁上,并拿走钥匙,第二个线程来的时候,发现被锁上,等待
//当票大于0张的时候卖票
if(count>0) {
System.out.println(Thread.currentThread().getName() + "卖出第" + count + "卖票");
count--;
}
}//执行完,归还钥匙
}
TicketRunnable.java
题外话:因为StringBuffer类下的方法append()添加字符串被加了synchronized锁,所以线程安全!!!同理可查看Vector集合和ArrayList集合下的add()方法,可以发现Vector集合下的add()方法线程安全,ArrayList集合下的add()方法线程不安全。
线程安全的类
安全: StringBuffer Vector
不安全:StringBuilder ArrayList
@Override
public synchronized StringBuffer append(String str) {
toStringCache = null;
super.append(str);
return this;
}
同步锁的第二种使用方式
a)创建锁对象 ReentrantLock lock = new ReentrantLock();
b)加锁和解锁使用tryfinally、lock.lock()、lock.unlock()
lock.lock(); //加锁
try {
if(count>0) {
System.out.println(Thread.currentThread().getName() + "卖出第" + count + "卖票");
count--;
}
}finally {
//不管上边代码时不时出现异常,都能保证解锁
lock.unlock(); //解锁
}
package com.Gary1;
import java.util.concurrent.locks.ReentrantLock;
public class TicketRunnable implements Runnable{
//设置有100张票 count不需要设置成static
private int count = 100;
private ReentrantLock lock= new ReentrantLock();
public void run() {
while(count>0) {
lock.lock(); //加锁
try {
if(count>0) {
System.out.println(Thread.currentThread().getName() + "卖出第" + count + "卖票");
count--;
}
}finally {
//不管上边代码时不时出现异常,都能保证解锁
lock.unlock(); //解锁
}
try {
Thread.sleep(100);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
TicketRunnable.java
买票问题升级(两种卖票方式,一种通过电影院窗口,一种通过手机App)
主要目的:保证两种方式使用的是同一把锁

package com.Gary1; //管理不同方式,单都是卖同一种资源
public class TicketMng { //表示票的剩余数量
public static int count = 100; }
TicketMng.java
package com.Gary1;
public class AppThread implements Runnable{
//保证使用同一个lock锁
public AppThread(Object lock) {
this.lock = lock;
}
private Object lock;
@Override
public void run() {
while(TicketMng.count>0) {
synchronized(lock) {
if(TicketMng.count>0) {
System.out.println(Thread.currentThread().getName()+"售出第"+TicketMng.count+"票");
TicketMng.count--;
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
AppThread.java
package com.Gary1;
public class WindowThread implements Runnable{
//保证使用同一个lock锁
public WindowThread(Object lock) {
this.lock = lock;
}
private Object lock;
@Override
public void run() {
while(TicketMng.count>0) {
synchronized(lock) {
if(TicketMng.count>0) {
System.out.println(Thread.currentThread().getName()+"售出第"+TicketMng.count+"票");
TicketMng.count--;
}
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
WindowThread.java
package com.Gary1;
public class GaryTest2 {
public static void main(String[] args) {
//windowThread和appThread共用同意一把锁
Object lock = new Object();
WindowThread windowThread = new WindowThread(lock);
AppThread appThread = new AppThread(lock);
new Thread(windowThread,"窗口").start();
new Thread(appThread,"手机APP").start();
}
}
GaryTest2.java
Java基础_通过模拟售票情景解决线程不安全问题的更多相关文章
- java基础_单例模式
java开发实战经典 --单例模式 从CSDN以及博客园的相关文章学习的,摘做笔记. “java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍三种:懒汉式单例.饿汉式单例.登记式 ...
- Java基础_线程的使用及创建线程的三种方法
线程:线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务. 进程:进 ...
- 【java基础 10】hash算法冲突解决方法
导读:今天看了java里面关于hashmap的相关源码(看了java6和java7),尤其是resize.transfer.put.get这几个方法,突然明白了,为什么我之前考数据结构死活考不过,就差 ...
- 【BigData】Java基础_创建一个订单类
需求描述 定义一个类,描述订单信息订单id订单所属用户(用户对象)订单所包含的商品(不定数量个商品对象)订单总金额订单应付金额: 总金额500~1000,打折85折 总金额1000~150 ...
- Java基础_死锁、线程组、定时器Timer
一.死锁问题: 死锁是这样一种情形:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放.由于线程被无限期地阻塞,因此程序不可能正常终止. 比如,线程一需要第一把所,此时锁处于空闲状态,给了 ...
- JAVA基础_泛型
什么是泛型 泛型是提供给javac编译器使用的,可以限定集合中的输入类型,让编译器挡住源程序中的非法输入,编译器编译带类型说明的集合时会去除掉"类型"信息,是程序的运行效率不受影响 ...
- java基础_集合List与Set接口
List接口继承了Collection的方法 当然也有自己特有的方法向指定位置添加元素 add(索引,添加的元素); 移除指定索引的元素 remove(索引) 修改指定索引的元素 set ...
- Java基础_基本语法
Java基本语法 一:关键字 在Java中有特殊含义的单词(50). 二:标志符 类名,函数名,变量名的名字的统称. 命名规则: 可以是字母,数字,下划线,$. 不能以数字开头. 见名之意. 驼峰规则 ...
- 32.Java基础_异常
JVM虚拟机默认异常处理机制 Java异常处理: 1.try...catch... 2.throw 1.try...catch... public class test{ public static ...
随机推荐
- 怎样退出mysql命令行
使用: mysql -u root -p 进入 mysql 命令号以后, 如果想退出, 可以使用: quit 命令, 如下: mysql -u root -p quit;
- Redis 消息队列 初体验
队列之生产者.消费者模式 using System; using System.Threading; using NServiceKit.Redis; namespace ConsoleApplica ...
- [NOIP10.5模拟赛]1.a题解--离散化+异或线段树
题目链接: 咕咕咕 https://www.luogu.org/problemnew/show/CF817F 闲扯 在Yali经历几天折磨后信心摧残,T1数据结构裸题考场上连暴力都TM没打满 分析 观 ...
- LeetCode 腾讯精选50题--链表排序
解题思路:归并 先把链表拆开,分为两部分,一直拆到只剩一个元素后,进行合并,利用一个临时节点记录重排后的链表的起始位置 合并不难,困难点在于如何拆分链表,自己的大体思路是利用两个指针,一个一次移动两位 ...
- eureka解析hostname为localhost问题 (转)
https://blog.csdn.net/liufei198613/article/details/79583686 公司的springcloud已经上线运行,但是最近测试环境老是会出现一个诡异的问 ...
- C++ STL 之 deque
deque 和 vector 的最大差异? 一在于 deque 允许常数时间内对头端进行元素插入和删除操作. 二在于 deque 没有容量的概念,因为它是动态的以分段的连续空间组合而成,随时可以增加一 ...
- 【坑】Java中遍历递归删除List元素
运行环境 idea 2017.1.1 需求背景 需要做一个后台,可以编辑资源列表用于权限管理 资源列表中可以有父子关系,假设根节点为0,以下以(父节点id,子节点id)表示 当编辑某个资源时,需要带出 ...
- 使用Google Thumbnails 压缩图片
背景说明:最近项目中需要用到一些图片文件的上传 ,但是有些图片很大,比如轮播图,大有的有几兆,这样加载一个首页都要很久,显然这样对用户体验是非常不友好的,对服务器资源将是一种浪费. 为了解决这个问题, ...
- 多线程threading模块
python的多线程编程 简介 多线程编程技术可以实现代码并行性,优化处理能力,同时功能的更小划分可以使代码的可重用性更好.Python中threading和Queue模块可以用来实现多线程编程. 详 ...
- JSONObject fromObject() 需要引入的包
1. maven项目 在pom.xml中添加以下依赖: <dependency> <groupId>net.sf.json-lib</groupId> <ar ...