如何高效實現多視窗賣票
阿新 • • 發佈:2018-11-11
多視窗賣票是常見的多執行緒問題,來看看要怎麼搞
方法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();
}
}
}