Java多线程(1)

Java多线程(一)

一、多线程的概念

 

    我们将正在执行的程序称为进程,现在的计算机大部分都支持多进程*。例如我们在上网的同时,还可以边听音乐或者边看视频。在只有一个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接口的方式。