继上回基于线程池的多线程售票demo,具体链接: http://www.cnblogs.com/xifenglou/p/8807323.html
以上算是单机版的实现,
至于分布式的项目就不能满足了,所以特别研究了一翻,采用redis 实现分布式锁机制, 实现了2.0版本。 使用redis setNx getSet方法 实现分布式锁,获取到锁的线程 将进行售票核心业务操作,具体见代码,欢迎讨论!
一.redis命令讲解:
 setnx()命令:

setnx的含义就是SET if Not Exists,其主要有两个参数 setnx(key, value)。

该方法是原子的,如果key不存在,则设置当前key成功,返回1;如果当前key已经存在,则设置当前key失败,返回0。

 get()命令:
get(key) 获取key的值,如果存在,则返回;如果不存在,则返回nil;
 getset()命令:
  这个命令主要有两个参数 getset(key, newValue)。该方法是原子的,对key设置newValue这个值,并且返回key原来的旧值。
假设key原来是不存在的,那么多次执行这个命令,会出现下边的效果:
1. getset(key, "value1")  返回nil   此时key的值会被设置为value1
2. getset(key, "value2")  返回value1   此时key的值会被设置为value2
3. 依次类推!
二.具体的使用步骤如下
1. setnx(lockkey, 当前时间+过期超时时间) ,如果返回1,则获取锁成功;如果返回0则没有获取到锁,转向2。
2. get(lockkey)获取值oldExpireTime ,并将这个value值与当前的系统时间进行比较,如果小于当前系统时间,则认为这个锁已经超时,可以允许别的请求重新获取,转向3。
3. 计算newExpireTime=当前时间+过期超时时间,然后getset(lockkey, newExpireTime) 会返回当前lockkey的值currentExpireTime。
4. 判断currentExpireTime与oldExpireTime 是否相等,如果相等,说明当前getset设置成功,获取到了锁。如果不相等,说明这个锁又被别的请求获取走了,那么当前请求可以直接返回失败,或者继续重试。
5. 在获取到锁之后,当前线程可以开始自己的业务处理,当处理完毕后,比较自己的处理时间和对于锁设置的超时时间,如果小于锁设置的超时时间,则直接执行delete释放锁;如果大于锁设置的超时时间,则不需要再锁进行处理。

import org.springframework.util.StopWatch;
import redis.clients.jedis.Jedis; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; /**
* 使用redis
* setnx getset 方式 实现 分布式锁
*
*/
public class TicketRunnable implements Runnable {
private CountDownLatch count;
private CyclicBarrier barrier;
private static final Integer Lock_Timeout = 10000;
private static final String lockKey = "LockKey";
private volatile boolean working = true; public TicketRunnable(CountDownLatch count,CyclicBarrier barrier) {
this.count = count;
this.barrier = barrier;
} private int num = 20; // 总票数 public void sellTicket(Jedis jedis) {
try{
boolean getLock = tryLock(jedis,lockKey, Long.valueOf(10));
if(getLock){
// Do your job
if (num > 0) {
System.out.print("=============="+Thread.currentThread().getName()+"=============== 售出票号" + num);
num--;
if(num!=0)
System.out.println(",还剩" + num + "张票--" );
else {
System.out.println(",票已经票完!--");
working = false;
}
}
}
}catch(Exception e){
System.out.println(e);
}finally {
try {
realseLock(jedis, lockKey);
Thread.sleep(600);
}catch (Exception e ) {
e.printStackTrace();
}
} } /**
* 获取锁
* @param jedis
* @param lockKey
* @param timeout
* @return
*/
public boolean tryLock(Jedis jedis,String lockKey, Long timeout) {
try {
Long currentTime = System.currentTimeMillis();//开始加锁的时间
boolean result = false; while (true && working) {
if ((System.currentTimeMillis() - currentTime) / 1000 > timeout) {//当前时间超过了设定的超时时间
System.out.println("---------------- try lock time out.");
break;
} else {
result = innerTryLock(jedis,lockKey);
if (result) {
System.out.println("=============="+Thread.currentThread().getName()+"=============== 获取到锁,开始工作!");
break;
} else {
System.out.println(Thread.currentThread().getName()+" Try to get the Lock,and wait 200 millisecond....");
Thread.sleep(200);
}
}
}
return result;
} catch (Exception e) {
e.printStackTrace();
return false;
}
} /**
* 释放锁
* @param jedis
* @param lockKey
*/
public void realseLock(Jedis jedis,String lockKey) {
if (!checkIfLockTimeout(jedis,System.currentTimeMillis(), lockKey)) {
jedis.del(lockKey);
System.out.println("=============="+Thread.currentThread().getName()+"=============== 释放锁!");
}
} /**
* 获取锁具体实现
* @param jedis
* @param lockKey
* @return
*/
private boolean innerTryLock(Jedis jedis,String lockKey) {
long currentTime = System.currentTimeMillis();//当前时间
String lockTimeDuration = String.valueOf(currentTime + Lock_Timeout + 1);//锁的持续时间
Long result = jedis.setnx(lockKey, lockTimeDuration); if (result == 1) { //返回1 代表第1次设置
return true;
} else {
if (checkIfLockTimeout(jedis,currentTime, lockKey)) {
String preLockTimeDuration = jedis.getSet(lockKey, lockTimeDuration); //此处需要再判断一次
if(preLockTimeDuration == null){ //如果 返回值 为空, 代表获取到锁 否则 锁被其他线程捷足先登
return true;
}else{
if (currentTime > Long.parseLong(preLockTimeDuration)) {
return true;
}
}
}
return false;
}
} /**
*
* @param jedis
* @param currentTime
* @param lockKey
* @return
*/
private boolean checkIfLockTimeout(Jedis jedis,Long currentTime, String lockKey) {
String value = jedis.get(lockKey);
if (value == null) {
return true;
}else{
if (currentTime > Long.parseLong(value)) {//当前时间超过锁的持续时间
return true;
} else {
return false;
}
} } @Override
public void run() {
System.out.println(Thread.currentThread().getName()+"到达,等待中...");
Jedis jedis = new Jedis("localhost", 6379); try{
barrier.await(); // 此处阻塞 等所有线程都到位后一起进行抢票
if(Thread.currentThread().getName().equals("pool-1-thread-1")){
System.out.println("---------------全部线程准备就绪,开始抢票----------------");
}else {
Thread.sleep(5);
}
while (num > 0) {
sellTicket(jedis);
}
count.countDown(); //当前线程结束后,计数器-1
}catch (Exception e){e.printStackTrace();} } /**
*
* @param args
*/
public static void main(String[] args) {
int threadNum = 5; //模拟多个窗口 进行售票
final CyclicBarrier barrier = new CyclicBarrier(threadNum);
final CountDownLatch count = new CountDownLatch(threadNum); // 用于统计 执行时长 StopWatch watch = new StopWatch();
watch.start();
TicketRunnable tickets = new TicketRunnable(count,barrier);
ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
//ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < threadNum; i++) { //此处 设置数值 受限于 线程池中的数量
executorService.submit(tickets);
}
try {
count.await();
executorService.shutdown();
watch.stop();
System.out.println("耗 时:" + watch.getTotalTimeSeconds() + "秒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} 运行结果如下:

火车票余量 可以在redis中获取,

这样就可以模拟多进程 多线程方式  共同访问redis中的变量。

有问题欢迎留言 探讨!

												

基于线程池的多线程售票demo2.0(原创)的更多相关文章

  1. 基于线程池的多线程售票demo(原创)

    废话不多说,直接就开撸import org.springframework.util.StopWatch;import java.util.concurrent.*;/** * 基于线程池实现的多线程 ...

  2. 基于线程池的多并发Socket程序的实现

    Socket“服务器-客户端”模型的多线程并发实现效果的大体思路是:首先,在Server端建立“链接循环”,每一个链接都开启一个“线程”,使得每一个Client端都能通过已经建立好的线程来同时与Ser ...

  3. 线程池,多线程,线程异步,同步和死锁,Lock接口

    线程池 线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源. 除了创建和销毁线程的开销之外,活动的线程也需要消耗系统资源.线程 ...

  4. 【Java TCP/IP Socket】基于线程池的TCP服务器(含代码)

    了解线程池 在http://blog.csdn.net/ns_code/article/details/14105457(读书笔记一:TCP Socket)这篇博文中,服务器端采用的实现方式是:一个客 ...

  5. java笔记--使用线程池优化多线程编程

    使用线程池优化多线程编程 认识线程池 在Java中,所有的对象都是需要通过new操作符来创建的,如果创建大量短生命周期的对象,将会使得整个程序的性能非常的低下.这种时候就需要用到了池的技术,比如数据库 ...

  6. 设计模式:基于线程池的并发Visitor模式

    1.前言 第二篇设计模式的文章我们谈谈Visitor模式. 当然,不是简单的列个的demo,我们以电商网站中的购物车功能为背景,使用线程池实现并发的Visitor模式,并聊聊其中的几个关键点. 一,基 ...

  7. requests模块session处理cookie 与基于线程池的数据爬取

    引入 有些时候,我们在使用爬虫程序去爬取一些用户相关信息的数据(爬取张三“人人网”个人主页数据)时,如果使用之前requests模块常规操作时,往往达不到我们想要的目的,例如: #!/usr/bin/ ...

  8. spring boot: 线程池ThreadPoolTaskExecutor, 多线程

    由于项目里需要用到线程池来提高处理速度,记录一下spring的taskExecutor执行器来实现线程池. ThreadPoolTaskExecutor的配置在网上找了很多解释没找到,看了下Threa ...

  9. Python网络爬虫之cookie处理、验证码识别、代理ip、基于线程池的数据爬去

    本文概要 session处理cookie proxies参数设置请求代理ip 基于线程池的数据爬取 引入 有些时候,我们在使用爬虫程序去爬取一些用户相关信息的数据(爬取张三“人人网”个人主页数据)时, ...

随机推荐

  1. 关于Redis数据库 ---- 基础篇

    Redis数据库也被称为数据结构数据库,因为存储基于key-value模式. 其中,value值可以为字符串(string),哈希(map),列表(list),集合(set)和有序集合(zset). ...

  2. JavaScript(第十一天)【变量,作用域,内存】

    JavaScript的变量与其他语言的变量有很大区别.JavaScript变量是松散型的(不强制类型)本质,决定了它只是在特定时间用于保存特定值的一个名字而已.由于不存在定义某个变量必须要保存何种数据 ...

  3. 第二次作业之微信小程序

    2.1 介绍产品相关信息 你选择的产品是? 微信小程序 为什么选择该产品作为分析? 在等待了1年多以后,小程序终于在今年初上线,即速应用在H5领域的累计,便承接在小程序上.8月7日,即速应用的用户微信 ...

  4. alpha-咸鱼冲刺day6

    一,合照 emmmmm.自然还是没有的. 二,项目燃尽图 三,项目进展 !!!QAQ可以做到跟数据库交互了!!!!先来撒花花!(然后继续甲板) (然后就没有进展了.翻车+1s) 四,问题困难 数据库交 ...

  5. [Android FrameWork 6.0源码学习] View的重绘过程之WindowManager的addView方法

    博客首页:http://www.cnblogs.com/kezhuang/p/关于Activity的contentView的构建过程,我在我的博客中已经分析过了,不了解的可以去看一下<[Andr ...

  6. ASP.NET MVC编程——单元测试

    1自动化测试基本概念 自动化测试分为:单元测试,集成测试,验收测试. 单元测试 检验被测单元的功能,被测单元一般为低级别的组件,如一个类或类方法. 单元测试要满足四个条件:自治的,可重复的,独立的,快 ...

  7. CSS你所不知的伪元素的用法

    你所不知的 CSS ::before 和 ::after 伪元素用法 博客分类: Div / Css / XML / HTML5   CSS 有两个说不上常用的伪类 :before 和 :after, ...

  8. Django-rest-framework源码分析----权限

    添加权限 (1)API/utils文件夹下新建premission.py文件,代码如下: message是当没有权限时,提示的信息 # utils/permission.py class SVIPPr ...

  9. angular路由守卫

     路由守卫是指当用户满足了某些要求之后才可以离开或者进入某个页面或者场景的时候使用.比如说只有当用户填写了用户名和密码之后才可以进入首页,比如说用户离开某个页面时明月保存信息提示用户是否保存信息后再离 ...

  10. 大数据学习总结(7)we should...

    大数据场景一.各种标签查询 查询要素:人.事.物.单位 查询范围:A范围.B范围.... 查询结果:pic.name.data from 1.痛点:对所有文本皆有实时查询需求2.难点:传统SQL使用W ...