我们来用最经典的卖票的案例,表明两种实现方式的区别,同时分析线程不安全产生的原因
一、继承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:出现了负数票:随机性和延迟导致的