线程的概念及简单实现

转载请注明出处:http://blog.youkuaiyun.com/yegongheng/article/details/38545343


初步

       在我们日常的开发工作中,透彻地理解和熟练地运用多线程技术对于每个开发人员来讲,其重要性不言而喻,因为几乎在任何一个稍微复杂一点的项目我们都需要使用多线程技术完成一些并发的操作。特别是随着多核处理器的发展,要想发挥多处理器系统的强大计算能力,熟练地掌握多线程的应用技巧变得十分有必要。那在接下来的一系列博文中,笔者将会重点围绕在Java环境下并发编程方面的知识进行深入地学习和研究。感兴趣的读者可以持续关注!


进程和线程的概念、关系和区别


         那在正式进入到多线程学习之前,我们先来简单了解了解进程和线程方面的知识: 一般来讲,一个运行中的应用程序可以看成是一个进程,那如果在电脑或手机上开启了多个应用程序的话,则系统需要开启多个进程执行多任务的操作。而对于线程概念的讨论则通常是在应用程序(进程上)层次上,举个例子来讲,我们在手机上使用宅男神器快播时,可以边看片边下载,即使同时下载多部X片,也毫不耽误我们看片的节奏。那作为程序员我们从实现角度来分析,上述功能该用什么技术实现呢?很显然,让人第一想起的当然是--多线程。通过开启多个子线程进行协同的操作来完成多个任务的下载,使得不影响我们包括看片的其它操作。不过我们对线程和进程的这些理解都只是停留在较为浅薄的认识上,那从较为专业的操作系统技术层面看,进程和线程之间到底有什么关系呢?它们之前有什么实质性的区别?对此我做了简单的总结,如下:

概念:

        进程:从操作系统核心角度来说,进程是操作系统调度和分配内存资源,CPU时间片的基本单位,它为正在运行应用程序提供了运行环境。简单来说,进程是应用程序的一次运行活动。

        线程:线程是程序内部的一个顺序代码流,它是CPU调度资源的最基本单位。

关系:

        (1) 线程并不能独立地执行,它必须依附在一个运行的应用程序上(即进程上),进程至少包含一个线程且可以包含多个线程,而线程只能依附于一个进程上;
        (2) 进程是操作系统资源管理的实体,而线程是进程的实体。

区别:

        当操作系统分配给进程资源后,同属于一个进程的多个线程之间可以相互共享进程中的内存资源,原因是线程没有自己独立的内存资源,它只有自己的执行堆栈和局部变量。而多个进程之间每个进程都拥有自己的一整套变量,即每个进程都拥有独立的内存单元。这就使得多线程之间的通信比多进程之间的通信更加容易和高效。

      基本了解了进程和线程的相关知识后,接下来我们着重学习在Java环境下的多线程编程的相关知识,首先是如何实现一个简单的多线程程序。

Java实现多线程的两种方式


        在Java程序设计中,创建一个线程的方式一般有两种:1、实现Runnable接口;2继承Thread类。这两种线程实现方式都有各自的特点和区别,那在实际的开发过程中,我们一般首选使用实现Runnable接口的方式来实现多线程的并发操作,因为其相比另外一种方式来讲,有更多优点,有哪些优点呢?我简单地做了如下总结:

        (1)避免了Java的单继承特性所带来的局限性;

        (2)适合线程间的代码和资源共享;

实现Runnable接口创建线程

         废话不多说了,接下来我们分别使用两种多线程的创建方式来实现一个非常经典的多线程例子--简易的火车票售票程序,首先使用第一种方式实现Runnable接口,具体的代码如下:

package com.androidleaf.multithreading.implementation;

public class ImplementationThread01 {
	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		/*----------------------实现多线程第一种方式:实现Runable接口----------------------------*/
		MyRunnable myRunnable = new MyRunnable();
		new Thread(myRunnable).start();
		new Thread(myRunnable).start();
		new Thread(myRunnable).start();
	}

}

class MyRunnable implements Runnable{

	private int tickets = 10;
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		while(true){
			synchronized (this) {
				if(tickets > 0){
					System.out.println("总票数:" + tickets + "  窗口号:"
							+ Thread.currentThread().getName()
							+ " 出售1张        剩下票数:" + --tickets);
				}else{
					System.out.println("车票已卖完!!");
					break;
				}
			}
			try {
				Thread.sleep(1500);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

       分析以上程序:我们开启三个线程,代表了三个售票窗口同时售票,模拟总的火车票的数量是10张。需要注意的是在程序中使用了synchronized同步代码块,该方面的知识后面的博文会详细讨论,在这里读者只需要明白它的作用是为了避免多个线程在执行过程中产生同步问题。在实现Runnable接口时,需要重写run()方法,在该方法里的操作便是在线程开启后所要完成的线程任务。接着我们需要做的就是开启一个线程,开启线程的操作很简单,只需要new 一个Thread对象,然后Thread对象调用start()方法即可。此时一个线程便成功开启,至于底层虚拟机是如何根据我们编写的代码与操作系统进行交互,从而开启一个线程的,我们不必知道,这些对于开发人员是透明的。

接下来运行程序,执行结果如下:


通过实验,我们发现,使用实现Runnable接口创建线程的方式可以很方便的实现线程之间变量资源的共享,三个线程同时对tcikets变量进行操作(为了避免线程同步问题,我们使用了synchronized关键字),当tickets变为0时,三个线程停止执行。

继承Thread类创建线程

接着我们再使用继承Thread类的方式来实现火车票票售票程序,具体的代码如下:

package com.androidleaf.multithreading.implementation;

public class ImplementationThread02 {

	public static void main(String[] args) {
		
		/*----------------------实现多线程第二种方式:继承Thread类----------------------------*/
	    new MyThread().start();
		new MyThread().start();
		new MyThread().start();
	}
}

class MyThread extends Thread{
	
	private int tickets = 5;
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		super.run();
		while(true){
			synchronized (this) {
				if(tickets > 0){
					System.out.println("总票数:" + tickets + "  窗口号:"
							+ Thread.currentThread().getName()
							+ "出售1张        剩下票数:" + --tickets);
				}else{
					System.out.println("车票已卖完!!");
					break;
				}
			}
			try {
				Thread.sleep(500);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}
代码和上面的代码基本相同,只不过将线程的实现方式从实现Runnable接口改为继承Thread类,并且我们将售票的总的票数改为了5张。那我们来运行一下程序,运行结果如下:


通过实验我们发现,在售票过程中,每一个线程都拥有自己独立的tickets变量。那这种方式对于线程之间需要进行资源共享的售票程序来讲,显然是不合理的。同样的,这样的情况在其它应用中也比较常见,所以呢,我们在实际的开发当中,尽量使用实现Runnable接口来创建新的线程,它的优点上面已经阐述过了。

Thread类源码剖析

       查看Thread类源代码,我们发现,其实Thread本身是实现了Runnable接口,那我们再看看Thread中的run()方法,代码如下:

 /**
     * If this thread was constructed using a separate
     * <code>Runnable</code> run object, then that
     * <code>Runnable</code> object's <code>run</code> method is called;
     * otherwise, this method does nothing and returns.
     * <p>
     * Subclasses of <code>Thread</code> should override this method.
     *
     * @see     #start()
     * @see     #stop()
     * @see     #Thread(ThreadGroup, Runnable, String)
     */
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
我们发现该run()方法并没有执行很多的操作,只是判断了target对象是否为null,如果不为null,则执行target对象的run()方法,那target对象又是从哪里创建的呢?我们分析代码,可以发现target对象其实是在实例化Thread对象时传入的Runnable接口对象,我们在实验第一个例子时就是将我们自定义的Runnable对象传入到Thread对象中,代码片段如下:

/*----------------------实现多线程第一种方式:实现Runable接口----------------------------*/
		MyRunnable myRunnable = new MyRunnable();
		new Thread(myRunnable).start();
		new Thread(myRunnable).start();
		new Thread(myRunnable).start();

分析上面的程序,我们可以发现,其实每当创建一个Thread线程时传入的Runnable对象都是同一个,很显然,三个线程执行过程中所处理的对象也都是同一个,那么就很好理解为什么用实现Runnable接口的方式来创建线程可以实现变量或资源的共享了,其本质就是这些线程都是处理的同一个Runnable对象。若我们每次创建线程时都传入不同的Runnable对象,则也无法达到线程间变量资源共享的目的了。

     那我们再来看看使用继承Thread类的方式来创建线程的代码,代码片段如下:

/*----------------------实现多线程第二种方式:继承Thread类----------------------------*/
	    new MyThread().start();
		new MyThread().start();
		new MyThread().start();

我们发现每次创建新的线程时都是新创建一个自定义的Thread对象,每个线程都执行各自独立的一套变量。再结合上面的火车票售票程序,我们就可以很容易地理解为什么三个线程没有共同操作一个tickets变量,而是拥有各自的tickets变量。

       通过上面对Thread类源码地简单分析,我们可知,其实不管是使用实现Runnable接口创建线程还是继承Thread类创建线程,它们这两种方式所实现的本质都是一样的,只不过前者利用接口的灵活性使得多个线程共同执行一个Runnable对象,从而达到变量或资源的共享。


小结:本篇博客主要介绍了一些多线程方面的入门知识,主要包括:(1)线程和进程的概念、关系和区别;(2)实现Runnable接口的方式创建线程;(3)继承Thread类的方式创建线程。下一篇博文我们将会对线程进行更加深入地分析,敬请期待!


源代码下载,请戳下面:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值