我們來用最經典的賣票的案例,表明兩種實現方式的區別,同時分析線程不安全產生的原因
一、繼承Thread類
package test; /** * @Description * @Author shusheng * @Email shusheng@yiji.com * @Date 2018/8/31 */ public class SellTicketOne extends Thread{ private static int tickets = 100; @Override public void run(){ while(true){ if(tickets>0){ System.out.println(getName()+"正在出售第"+tickets+"張票"); tickets--; } } } }
二、實現Runnable接口
package test; /** * @Description * @Author shusheng * @Email shusheng@yiji.com * @Date 2018/8/31 */ public class SellTicketTwo implements Runnable{ private static int tickets = 100; @Override public void run(){ while(true){ if(tickets>0){ System.out.println(Thread.currentThread().getName()+"正在出售第"+tickets+"張票"); tickets--; } } } }
啟動線程
package test; /** * @Description * @Author shusheng * @Email shusheng@yiji.com * @Date 2018/8/31 */ public class SellTicketTest { public static void main(String[] args) { SellTicketOne one1 = new SellTicketOne(); SellTicketOne one2 = new SellTicketOne(); SellTicketOne one3 = new SellTicketOne(); one1.setName("窗口一"); one2.setName("窗口二"); one3.setName("窗口三"); one1.start(); one2.start(); one3.start(); SellTicketTwo two = new SellTicketTwo(); Thread t1 = new Thread(two,"窗口四"); Thread t2 = new Thread(two,"窗口五"); Thread t3 = new Thread(two,"窗口六"); t1.start(); t2.start(); t3.start(); } }
可以看到,二者的主要區別是:
1.實現Runnable接口的方式可以避免由於JAVA單繼承帶來局限性
2.實現Runnable接口的方式,適用多個相同程序的代碼去處理同一個資源的情況,把線程同程序的代碼、數據有效分離,較好的體現了面向對象的設計思想。
加上每次賣票延遲200毫秒,運行程序,發現兩個問題:
A:相同的票賣了多次:CPU的一次操作必須是原子性
B:出現了負數票:隨機性和延遲導致的