版权声明:本文为博主原创文章,欢迎转载,转载请注明本文链接! 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();
}
}
}