如何高效实现多窗口卖票

版权声明:本文为博主原创文章,欢迎转载,转载请注明本文链接! https://blog.csdn.net/qq_38238041/article/details/83796203

多窗口卖票是常见的多线程问题,来看看要怎么搞

方法1,不建议的使用方式

package concurrent.me.ticket;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 多线程卖票:5个窗口(线程)进行卖票,一共100张票
 * 方法1 - 没有使用任何同步,线程是不安全的
 * @author BarryLee
 * @2018年11月6日@下午11:09:49
 */
public class Ticket1 {
	//初始化票
	private static List<String>tickets = new ArrayList<>();
	static {
		for(int i = 0;i<100;i++) {
			tickets.add("票:"+i);
		}
	}
	//测试
	public static void main(String[] args) {
		for(int i = 0;i<5;i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					while(true) {
						//判断是否还有票
						if(tickets.size()>0) {
							//有的话就睡一下,模拟这个位置有很多任务,将问题暴露出来
							try {
								TimeUnit.MILLISECONDS.sleep(2);
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
							System.out.println(tickets.remove(0));
						}else break;
					}
				}
			}).start();;
		}
	}
}
/*
票:2
票:4
票:0
票:0
票:3
票:5
票:9
票:7
票:6
票:5
票:9
票:14
票:13
票:10
票:10
票:15
票:18
票:17
票:16
票:15
票:20
票:22
票:24
票:21
票:21
票:25
票:28
票:28
票:25
票:25
票:29
票:33
票:29
票:29
票:33
票:34
票:34
票:36
票:34
票:34
票:37
票:41
票:39
票:37
票:37
票:41
票:47
票:46
票:44
票:41
票:48
票:52
票:50
票:50
票:50
票:52
票:59
票:58
票:54
票:52
票:60
票:63
票:62
票:62
票:61
票:65
票:66
票:65
票:65
票:65
票:70
票:72
票:70
票:70
票:73
票:75
票:78
票:76
票:76
票:75
票:79
票:84
票:80
票:80
票:79
票:84
票:88
票:85
票:84
票:89
票:89
票:92
票:90
票:89
票:93
票:94
票:98
票:97
票:96
票:97
票:99
票:99
票:99
票:99
票:99
票:99
票:99
票:99
票:99
票:99
票:99
票:99
票:99
票:99
票:99
票:99
票:99
票:99
票:99
票:99
票:99
Exception in thread "Thread-0" Exception in thread "Thread-4" Exception in thread "Thread-2" Exception in thread "Thread-3" java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.ArrayList.rangeCheck(ArrayList.java:657)
	at java.util.ArrayList.remove(ArrayList.java:496)
	at concurrent.me.ticket.Ticket1$1.run(Ticket1.java:36)
	at java.lang.Thread.run(Thread.java:748)
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.ArrayList.rangeCheck(ArrayList.java:657)
	at java.util.ArrayList.remove(ArrayList.java:496)
	at concurrent.me.ticket.Ticket1$1.run(Ticket1.java:36)
	at java.lang.Thread.run(Thread.java:748)
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.ArrayList.rangeCheck(ArrayList.java:657)
	at java.util.ArrayList.remove(ArrayList.java:496)
	at concurrent.me.ticket.Ticket1$1.run(Ticket1.java:36)
	at java.lang.Thread.run(Thread.java:748)
java.lang.IndexOutOfBoundsException: Index: 0, Size: 0
	at java.util.ArrayList.rangeCheck(ArrayList.java:657)
	at java.util.ArrayList.remove(ArrayList.java:496)
	at concurrent.me.ticket.Ticket1$1.run(Ticket1.java:36)
	at java.lang.Thread.run(Thread.java:748)

*/

方法2,使用比较原始的同步容器Vector,同样也会出问题

package concurrent.me.ticket;

import java.util.Vector;
import java.util.concurrent.TimeUnit;

/**
 * 多线程卖票:5个窗口(线程)进行卖票,一共100张票
 * 方法2 - 使用了Vector,Vector是线程安全的,但是在这里也会出问题
 * @author BarryLee
 * @2018年11月6日@下午11:09:49
 */
public class Ticket2 {
	//初始化票
	private static Vector<String> tickets = new Vector<String>();
	static {
		for(int i = 0;i<100;i++) {
			tickets.add("票:"+i);
		}
	}
	//测试
	public static void main(String[] args) {
		for(int i = 0;i<5;i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					while(true) {
						//判断是否还有票
						if(tickets.size()>0) {
							//有的话就睡一下,模拟这个位置有很多任务,将问题暴露出来
							try {
								TimeUnit.MILLISECONDS.sleep(2);
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
							/* 问题就在这里:tickets.size()是安全的,tickets.remove()也是安全的
							 * 但是这两个方法加起来的时候并没有原子性
							 * 当容器还有最后一个元素的时候,一个线程来访问,发现还有,然后就去拿,还没拿到
							 * 然后这时CPU被第二个线程占用的,他也去访问,也发现容器中还有票,也去拿票
							 * 然后其中一个拿到了票,其余的线程去拿的时候容器已经是空的了,然后就出bug了!!
							 */
							System.out.println(tickets.remove(0));
						}else break;
					}
				}
			}).start();;
		}
	}
}
/*
票:0
票:4
票:3
票:2
票:1
票:5
票:9
票:8
票:7
票:6
票:10
票:14
票:13
票:12
票:11
票:15
票:19
票:18
票:17
票:16
票:20
票:24
票:23
票:21
票:22
票:25
票:29
票:28
票:27
票:26
票:30
票:33
票:34
票:32
票:31
票:35
票:39
票:38
票:37
票:36
票:40
票:43
票:41
票:44
票:42
票:45
票:49
票:48
票:47
票:46
票:50
票:54
票:51
票:52
票:53
票:55
票:59
票:58
票:57
票:56
票:60
票:64
票:63
票:61
票:62
票:65
票:66
票:68
票:69
票:67
票:70
票:71
票:74
票:72
票:73
票:75
票:79
票:78
票:77
票:76
票:80
票:84
票:83
票:82
票:81
票:85
票:86
票:87
票:89
票:88
票:90
票:91
票:93
票:92
票:94
票:95
票:96
票:97
票:98
票:99
Exception in thread "Thread-3" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 0
	at java.util.Vector.remove(Vector.java:834)
	at concurrent.me.ticket.Ticket2$1.run(Ticket2.java:35)
	at java.lang.Thread.run(Thread.java:748)
*/

方法3,这个方法OK,但是效率较低

package concurrent.me.ticket;

import java.util.Vector;
import java.util.concurrent.TimeUnit;

/**
 * 多线程卖票:5个窗口(线程)进行卖票,一共100张票
 * 方法3 - 使用synchronized锁来解决上一个例子的问题
 * 缺点是相对来讲比较慢
 * @author BarryLee
 * @2018年11月6日@下午11:09:49
 */
public class Ticket3 {
	//初始化票
	private static Vector<String> tickets = new Vector<String>();
	static {
		for(int i = 0;i<100;i++) {
			tickets.add("票:"+i);
		}
	}
	//测试
	public static void main(String[] args) {
		for(int i = 0;i<5;i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					while(true) {
						synchronized (tickets) {
							if(tickets.size()>0) {
								try {
									TimeUnit.MILLISECONDS.sleep(2);
								} catch (InterruptedException e) {
									e.printStackTrace();
								}
								System.out.println(tickets.remove(0));
							}else break;
						}
					}
				}
			}).start();;
		}
	}
}

方法4,最后一个了,也就是建议的写法,上一个能找到工作,但这个能找到好工作哈哈哈

package concurrent.me.ticket;

import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.TimeUnit;

/**
 * 多线程卖票:5个窗口(线程)进行卖票,一共100张票
 * 方法4,这个是正确的操作了,效率相对synchronized较高而且不会出现前面的问题
 * @author BarryLee
 * @2018年11月6日@下午11:30:36
 */
public class Ticket4 {
	//初始化容器 - 使用jdk1.5之后新增的并发容器ConcurrentLinkedQueue
	//底层的add方法有一个checkNotNull(e)方法,所以当add(null)的时候会抛出异常,null是添加不进去的
	private static Queue<String>queue = new ConcurrentLinkedQueue<>();
	static {
		for(int i = 0;i<100;i++) {
			queue.add("票:"+i);
		}
	}
	public static void main(String[] args) {
		for(int i = 0;i<10;i++) {
			new Thread(new Runnable() {
				@Override
				public void run() {
					while(true) {
						String s = queue.poll();
						/*
						 * 这里为什么不会出现问题呢?
						 * 因为这里是先取出queue中的元素,然后再判断
						 * 而queue中一定是不能存放null值的
						 * 顶多就s==null而break出去
						 * (想想Ticket1、2,这两个都是先判断,然后再拿)
						 */
						try {
							TimeUnit.MILLISECONDS.sleep(2);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						if(s==null) {
							break;
						}
						System.out.println(s);
					}
				}
			}).start();
		}
	}
}

猜你喜欢

转载自blog.csdn.net/qq_38238041/article/details/83796203