Java多线程(1)
一、多线程的概念
我们将正在执行的程序称为进程,现在的计算机大部分都支持多进程*。例如我们在上网的同时,还可以边听音乐或者边看视频。在只有一个CPU的电脑上,是无法同时运行多个进程的,而是CPU轮流执行多个程序,看上去像是多个进程在同时执行。
上面是多进程的例子。但是,我们在程序内部,也可以同时运行多个任务,我们将在一个进程内部运行的每一个任务都称为一个线程(Thread)。在某个时间点上,CPU运行的线程只能有一个。
二、线程的使用范围
多线程应用无处不在,但应用最频繁的是游戏开发和网络编程。
三、如何创建线程:
在Java中,线程也是一种对象,但并非任何对象都可以成为线程,只有实现了Runnable接口的类才可以称为线程。我们可以通过两种方式来创建一个线程对象。
(1)让一个类实现Runnable接口,实现该接口的run()方法
package buaa.threaddemo; public class ThreadDemo implements Runnable{ /** * 实现了Runnable的run()方法,这个方法是在线程对象获得CPU的使用权的时候真正运行的方法, * 系统会自动调用的,我们无需直接调用run()方法。 */ public void run() { for (int i = 0; i < 100; i++) { System.out.println("---->"+Thread.currentThread().getName()+i); } } public static void main(String[] args) { //创建一个ThreadDemo对象 ThreadDemo threadDemo = new ThreadDemo(); //创建线程对象thread1,并给线程thread1起个名字 Thread thread1 = new Thread(threadDemo,"线程一"); //创建线程对象thread2,没有给线程对象thread2起名字,所以系统会提供默认的名字,即Thread-编号 Thread thread2 = new Thread(threadDemo); //启动一个线程,注意:启动线程的方式是通过Thread类的start()方法来启动一个线程 thread1.start(); //启动另一个线程 thread2.start(); } }
注:Thread.currentThread().getName()的作用是得到当前线程的名字。
(2)让一个类继承java.lang.Thread类,由于Thread类本身已经实现了Runnable接口,所以通过继承该类,也可以将一个普通类转变为线程类。
package buaa.threaddemo; public class ThreadDemo2 extends Thread{ public void run(){ for (int i = 0; i < 100; i++) { System.out.println("---->"+i); } } public static void main(String[] args) { //创建一个ThreadDemo2对象 ThreadDemo2 threadDemo = new ThreadDemo2(); //启动线程 threadDemo.start(); } }
两种创建线程的方式对比分析:
既然直接继承Thread类和实现Runnable接口都能实现多线程,那么这两种实现多线程的方式在应用上有什么区别吗?我们到底用哪个好呢?
为了回答这个问题我们通过编写一个应用程序来进行分析比较。我们用程序来模拟铁路售票系统,实现通过4个售票点发售某日某次列车的100张车票,一个售票点用一个线程来表示。
package buaa.threaddemo; public class ThreadSellTicket extends Thread{ private int tickets = 100; public void run(){ while (tickets > 0) { String threadName = Thread.currentThread().getName(); System.out.println(threadName + "is saling ticket " + tickets--); } } } package buaa.threaddemo; public class ThreadTest { public static void main(String[] args) { ThreadSellTicket thread = new ThreadSellTicket(); thread.start(); thread.start(); thread.start(); thread.start(); } }
在上面代码中,我们用ThreadSellTicket类模拟售票处的售票过程,run方法的每次循环都将总票数减一,模拟卖出一张车票,同时将该车票号打印出来,直到剩余票数为0为止。在ThreadTest类的main方法中,我们创建了一个线程对象,并重复启动四次,希望通过这个方式产生四个线程,结果怎样呢?
从运行结果上看,我们发现其实只有一个线程在运行,这个结果告诉我们:一个线程对象只能启动一个线程,无论你调用多少遍start()方法,结果都只有一个线程。
我们接着修改ThreadTest,在main方法中创建四个ThreadSellTicket对象:
package buaa.threaddemo; public class ThreadTest { public static void main(String[] args) { ThreadSellTicket thread1 = new ThreadSellTicket(); ThreadSellTicket thread2 = new ThreadSellTicket(); ThreadSellTicket thread3 = new ThreadSellTicket(); ThreadSellTicket thread4 = new ThreadSellTicket(); thread1.start(); thread2.start(); thread3.start(); thread4.start(); } } 部分运行结果: Thread-0is saling ticket 98 Thread-0is saling ticket 97 Thread-0is saling ticket 96 Thread-3is saling ticket 99 Thread-2is saling ticket 83 Thread-1is saling ticket 99 Thread-2is saling ticket 82
从上面结果上看,每个票号都被打印了四遍,即四个线程各自卖各自的100张票,而不是去卖共的100张票。
这种情况是怎样造成的呢?我们需要的是,多个线程去处理同一资源,一个资源只能对应一个对象,在上面的程序中,我们创建了四个ThreadSellTicket对象,就等于创建了四个资源,每个ThreadSellTicket都有100张票,每个线程在独立的处理各自的资源。
经过分析,我们可以总结出,要实现这个铁路售票模拟程序,我们只能创建一个资源对象,但要创建多个线程去处理这同一资源对象,并且每个线程上所运行的都是相同的代码。
package buaa.threaddemo; public class ThreadSellTicket2 implements Runnable{ private int tickets = 100; public void run() { while (tickets > 0) { String threadName = Thread.currentThread().getName(); System.out.println(threadName + "is saling ticket " + tickets--); } } } package buaa.threaddemo; public class ThreadTest { public static void main(String[] args) { ThreadSellTicket2 tickets = new ThreadSellTicket2(); Thread thread1 = new Thread(tickets); Thread thread2 = new Thread(tickets); Thread thread3 = new Thread(tickets); Thread thread4 = new Thread(tickets); thread1.start(); thread2.start(); thread3.start(); thread4.start(); } }
在上面程序中,我们创建了四个线程,每个线程调用的是同一个ThreadSellTicket2对象中的run()方法,访问的是同一个对象中的变量(tickets)的实例,这个程序满足了我们的需求。
可见,实现Runnable接口相对于继承Thread类来说,有如下好处:
1、适合多个相同程序代码的线程去处理同一资源的情况,把线程同程序代码,数据有效分离,较好的体现了面向对象的设计思想。
2、可以避免由于Java的单继承特性带来的局限。我们经常碰到这样一种情况,即当我们要将已经继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,那么,这个类就只能采用实现Runnable接口的方式。