前面我们学习了进程的两种实现方式,第一种:继承Thread类,第二种:实现Runnable接口,首先我们来对比一下两种实现方式:
第一种方式:
1)自定义MyThread 类继承自Thread
2)重写Thread类中的run()方法
3)创建MyThread类对象,分别去启动线程
注意:启动线程的时候不调用run()方法,因为run()不能作为启动线程的方法,该方法的调用相当于调用一个普通方法,并不会出现线程执行的一种随机性!所以启动线程用的是start()方法,start方法的执行是通过JVM调用run方法,但一个进程不要连续启动,否则会出现非法线程状态异常!
第二种方式:
1)自定义MyRunnable类实现Runnable接口
2)实现接口的run()方法
3)创建MyRunnable类对象,创建Thread类对象,将MyRunnable类对象作为参数进行传递,分别启动线程。
那么你可能会问:既然已经有了第一种实现方式,为什么还会有第二种呢?因为第二种方式实际上是优于第一种的
1)避免了Java单继承的一种局限性
2)更符合Java面向对象的一种设计原则:面向接口编程。将代码的实现和资源对象(MyRunnable)有效地分离开来(数据分离原则)
接下来说一下线程的生命周期:线程从开始创建的时候,一直到线程的执行,最后到线程的终止!
新建线程:此时线程没有执行执行资格,没有执行权。
线程就绪:线程有执行资格了,但是没有执行权,一旦该线程抢到了CPU的执行权,线程就开始执行了。
在执行线程之前,线程还可能会阻塞,如sleep()和wait()方法,此时线程处于阻塞状态,睡眠时间到了或执行notify()方法来唤醒进程,线程对象.start();
线程执行:线程有执行资格,并且有执行权,此时该线程如果被别的线程抢占到了CPU的执行权,线程就处于就绪的状态。
线程死亡:线程执行完毕,会被垃圾回收线程中的垃圾回收器及时从内存中释放掉!
这次我们就通过这两种方式来实现一个电影院售票的小案例:
需求:某电影院出售某些电影的票(复联3,红高粱....),有三个窗口同时进行售票(100张票),请您设计一个程序,模拟电影院售票
两种方式:
继承
接口
方式一:
//SellTicket线程
public class SellTicket extends Thread {
//为了不让外界更改这个类中的数据,用private修饰
//要让每一个线程都使用同一个数据,应该用static修饰
private static int tickets = 100 ;
@Override
public void run() {
//为了模拟电影卖票(模拟一直有票)
//死循环
//st1,st2,st3都有执行这个里面的方法
while(true) {
if(tickets>0) {
System.out.println(getName()+"正在出售第"+(tickets--)+"张票");
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
//创建三个子线程,分别代码三个窗口
SellTicket st1 = new SellTicket() ;
SellTicket st2 = new SellTicket(