现在的位置: 首页 > 综合 > 正文

【重磅出击】 java入门到精通——多线程(上)

2013年06月04日 ⁄ 综合 ⁄ 共 3799字 ⁄ 字号 评论关闭
文章目录

Java 多线程(上)

线程和进程的区别

1.1进程和线程

          进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以有多个线程。比如在Windows系统中,一个运行的xx.exe就是一个进程。Java程序的进程里有几个线程:主线程, 垃圾回收线程(后台线程)。

     线程是指进程中的一个执行任务(控制单元),一个进程中可以运行多个线程,多个线程可共享数据。多进程:操作系统中同时运行的多个程序,在同一个进程中同时运行的多个任务;一个进程至少有一个线程,为了提高效率,可以在一个进程中开启多个控制单元。
 

     并发运行。如:多线程下载软件。 

     多线程下载:此时线程可以理解为下载的通道,一个线程就是一个文件的下载通道,多线程也就是同时开起好几个下载通道.当服务器提供下载服务时,使用下载者是共享带宽的,在优先级相同的情况下,总服务器会对总下载线程进行平均分配。不难理解,如果你线程多的话,那下载的越快。现流行的下载软件都支持多线程。可以完成同时运行,但是通过程序运行的结果发现,虽然同时运行,但是每一次结果都不一致。
 

因为多线程存在一个特性:随机性。  

造成的原因:CPU在瞬间不断切换去处理各个线程而导致的。  

可以理解成多个线程在抢cpu资源。  

1.2 线程与进程的比较

     线程具有许多传统进程所具有的特征,故又称为轻型进程进程元而把传统的进程称为重型进程,它相当于只有一个线程的任务。在引入了线程的操作系统中,通常一个进程都有若干个线程,至少需要一个线程。

进程与线程的区别:

     1.进程有独立的进程空间,进程中的数据存放空间(堆空间和栈空间)是独立的

     2.线程的堆空间是共享的,栈空间是独立的,线程消耗的资源也比进程小,相互之间可以影响的。

     

    如果想详细了解线程与进程请看我的《线程与进程的详细分析》。

2. 创建线程

   2.1 创建线程第一种方式(继承):

     1.  新建一个类,继承Thread

     2.  复写 run方法

     3.  创建一个线程对象

     4.  启动线程(线程对象.start())

代码如下:

class MyThread extends Thread{		
	public void run() {

	for (int i = 0; i < 100; i++) {
		System.out.println("MyThread---->"+ i);
	}
	}
}
class ThreadDemo1 {
public static void main(String[] args) {
	for (int i = 0; i < 100; i++) {			
		System.out.println("main------>" +i);
		if(i == 10){				
			new MyThread().start();
		}
	}
    }
}

   2.2 创建线程第二种方式(实现):

      实现Runnable接口
     1.  子类覆盖接口中的run方法。
     2.  通过Thread类创建线程,并将实现了Runnable接口的子类对象作为参数传递给Thread类的构造函数。
     3.  Thread类对象调用start方法开启线程。

代码如下:
class MyThread2 implements Runnable {
	public void run() {
		// 线程体
		for (int i = 0; i < 100; i++) {
			System.out.println("MyThread2----->" + i);
		}
	}
}
public class ThreadDemo2 {
	public static void main(String[] args) {
		for (int i = 0; i < 100; i++) {
			System.out.println("main-->" +i);
			if (i == 10) {
				new Thread(new MyThread2()).start();
			}
		}
	}
}

         现在我们通过一个经典案例来说明这两种方式的区别:

售票的例子,这个例子基本上每一本java入门书上都会有!

需求:有50张票需要3个售票窗口卖出;用两种开启线程方式买票,观察两种方式买票的结果有什么不同?

   1.  继承Thread方式

class Ticket1 extends Thread{	
	int num = 20;	
	public Ticket1(String name){
		super(name);
	}
	public void run() {		
		for (int i = 0; i < 100; i++) {
			if(num >0) {
				System.out.println(getName()+"卖出第" +num-- +"张");
			}
		}
	}
}
public class TicketDemo {
	public static void main(String[] args) {
		//3个窗口买
		new Ticket1("售票员-1").start();
		new Ticket1("售票员-2").start();
		new Ticket1("售票员-3").start();
	}
  }

输出结果的一部分:

 售票员-1卖出第50张

  售票员-3卖出第50张

  售票员-2卖出第50张

  售票员-3卖出第49张

  售票员-1卖出第49张

  售票员-3卖出第48张


  售票员-3卖出第47张


   2.  实现Runnable方式

class Ticket2 extends Object implements Runnable{	
	int num = 20;
	public void run() {
		for (int i = 0; i < 50; i++) {
			if(num >0) {
				System.out.println(Thread.currentThread().getName()+"卖出第" +num-- +"张");
			}
		}
	}	
}
public class TicketDemo2 {
	public static void main(String[] args) {
		Runnable target = new Ticket2();
		new Thread(target,"售票员-1").start();
		new Thread(target,"售票员-2").start();
		new Thread(target,"售票员-3").start();
	}
}

输出结果的一部分:

    

   售票员-1卖出第50张

   售票员-3卖出第48张

   售票员-2卖出第49张

   售票员-3卖出第46张

   售票员-2卖出第45张

   售票员-2卖出第43张


currentThread():返回对当前正在执行的线程对象的引用。

getName():获取线程名称。

setName()设置线程名字。

将两种方法的输出结果进行比较,我们会发现

     继承Thread类的输出结果一共打印了150条,说明一条票被卖了三次,这显然是不正确的。

   而实现Runnable接口却没有出现这样的问题。

   

解释:

    因为一个线程只能启动一次,通过Thread实现线程时,线程和线程所要执行的任务是捆绑在一起的。也就使得一个任务只能启动一个线程,不同的线程执行的任务是不相同的,所以没有必要,也不能让两个线程共享彼此任务中的资源。

    一个任务可以启动多个线程,通过Runnable方式实现的线程,实际是开辟一个线程,将任务传递进去,由此线程执行。可以实例化多个 Thread对象,将同一任务传递进去,也就是一个任务可以启动多个线程来执行它。这些线程执行的是同一个任务,所以他们的资源是共享。

两种不同的线程实现方式本身就决定了其是否能进行资源共享

继承Thread

同份资源不共享并且由于java的单继承,程序以后不便于扩展。

实现Runnable:(推荐)

多个线程共享一个目标资源,适合多线程处理同一份资源。

该类还可以继承其他类,也可以实现其他接口。


线程的生命周期


   

3.1 线程的生命周期之新建和就绪状态

新建:当程序使用new创建一个线程后,该线程处于新建状态,此时他和其他java对象一样,仅仅由Java虚拟机为其分配内存并初始化成员变量值。【 Thread r = new Thread() 】

就绪:当线程对象调用start()方法后,该线程处于就绪状态,线程计入线程队列排队,此时该状态线程并未开始执行,它仅表示可以运行了。至于该线程何时运行,取决于JVM线程调度器的调度。【 r.start() 】

3.2 线程的生命周期之运行和阻塞状态

运行:若处于就绪状态的线程获得了CPU,开始执行run()线程执行体,该线程处于执行状态。

阻塞:线程运行过程中需要被中断,目的是是其他的线程获得执行的机会。该状态就会进入阻塞状态。

注意:阻塞状态不能直接转成运行状态,阻塞状态只能重新进入就绪状态。

3.3 线程的生命周期之死亡

run()执行完成,线程正常结束; 线程抛出未捕获的Exception或Error;

调用线程的stop()。(易导致死锁,不推荐)

注意:

主线程结束后,其他线程不受其影响,不会随之结束;

一旦子线程启动起来后,就拥有和主线程相等地位,不受主线程影响。

测试线程是否活着,可用线程对象的isAlive()方法。当线程处于就绪,运行,阻塞状态返回true。当线程处于新建和死亡状态,返回false。

已死亡的线程是不可以通过start()方法唤醒线程的,否则引发IllegalThreadStateException异常;

       楼猪不是什么大牛,但自我评价觉得还总结的不错,挺适合新手。 如果发现哪里错了,望各位大牛指点!

抱歉!评论已关闭.