两种线程实现方法的比较
(1)实现线程的方法一
线程类继承Thread类,重写run()方法,见代码12.6的第1行到第6行。在main线程中,先创建线程类对象,然后调用start()方法启动线程,见代码12.6的第9行、第10行。
【代码12.6】
(2)实现线程的方法二
任务类实现Runnable接口和run()方法,见代码12.7的第1行到第6行。在main线程中,先创建任务类对象,再以任务类对象为传入参数创建Thread线程对象,然后对线程对象调用start()方法启动线程,见代码12.7的第9行到第11行。
【代码12.7】
两种实现线程的方法在多道线程各自独立没有关联的时候,运行效果是相同的,采用哪种方法都可以。但是,因为Java是单继承(一个类最多只能继承一个父类),所以当需要继承其他父类时,就只能采用方法二。
当多道线程出现数据共享时,两种方法的运行情况会不同,请看如下案例:车站当前共有车票数5张,分别有3个窗口同时并行卖票,用多线程模拟卖票过程。
【代码12.8】采用线程实现方法一
代码12.8的运行结果:
第一窗口卖掉了第5张票
第三窗口卖掉了第5张票
第二窗口卖掉了第5张票
第三窗口卖掉了第4张票
第一窗口卖掉了第4张票
第三窗口卖掉了第3张票
第二窗口卖掉了第4张票
第三窗口卖掉了第2张票
第一窗口卖掉了第3张票
第三窗口卖掉了第1张票
第二窗口卖掉了第3张票
第一窗口卖掉了第2张票
第一窗口卖掉了第1张票
第二窗口卖掉了第2张票
第二窗口卖掉了第1张票
我们可以发现运行结果是不正确的,在这个运行结果中,不是3个窗口一起卖5张票,而是每个窗口各自卖5张票。那么怎样让3个窗口共享5张票呢?请看第二种实现方法。
【代码12.9】采用线程实现方法二
代码12.9的运行结果:
第一窗口卖掉第4张票
第三窗口卖掉第3张票
第二窗口卖掉第5张票
第三窗口卖掉第1张票
第一窗口卖掉第2张票
方法二不会像方法一那样,每个窗口各卖5张票,方法二是三个窗口一起卖5张票。在方法一中,每个线程各自有一个tickets成员,所以3道线程没有共享tickets。在方法二中,只创建了一个任务类对象,3道线程共享同一个任务类对象,这样也就共享了同一个任务中的tickets。因此,在多线程之间有数据共享的时候,要采用方法二。但是,方法二还有可能会出现问题,其运行结果可能会出现一张票被重复卖多次的不合理情况,这涉及后面要讲的线程不安全。