您的位置:首页 > 职场人生

黑马 程序员——Java基础---多线程

2015-03-24 10:29 295 查看

黑马程序员——Java基础---多线程

------Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ------

一、概述

  对于Java而言,可以在一个程序中并发地启动多个线程,让这些线程在多处理器上同时运行。在单处理器系统中,多个线程共享CPU时间称为时间共享,而操作系统负责调度及分配资源给它们,即使是单处理器,多线程程序的运行速度也比单线程程序更快。
当程序作为一个应用程序运行时,至少拥有一个线程,那就是Java虚拟机在main方法中启动的一个线程,称为主线程。Java语言提供了非常优秀的多线程支持,程序可以通过非常简单的方式启动多线程。下面将会比较详细的介绍包括创建、启动线程、控制线程以及线程的同步操作。

二、正文

1、线程的创建和启动

1.1 继承Thread类创建线程类

  通过继承Thread类创建启动多线程步骤如下:

定义Thread类的子类,并重写该类的run()方法,该run方法的方法体就是需要完成的任务。

创建Thread子类的实例,即创建了线程对象。

调用线程对象的start()方法来启动该线程。

下面是一段创建Thread线程的代码:

class Test extends Thread {

String name;

public TimePrinter(String name) {

this.name = name;

}

//复写run方法

public void run() {

//需要执行的方法

System.out.print(name);

}

}

class ThreadTest{

static public void main(String args[]) {

//创建teat对象,并启动线程

Test test = new Test("zhang");

test.start();

}

}


  本例简单的介绍了Thread线程的创建方法,但是有时候作为线程运行的类是某类型的子类,就不能通过继承Thread来创建线程,因为java只支持单继承,对于这种情况,就得用到Runnable接口了。

1.2 实现Runnable接口创建线程类

实现Runnable接口来创建并启动多线程的步骤如下:

定义Runnable接口的实现类,并重写该接口的run()方法,该run方法的方法体就是该线程的执行体。

创建Runnable实现类的实例,并以此实例作为创建Thread实例的参数传入

下面是实现Runnable接口来创建线程代码:

class Test implements Runnable {

String name;

public TimePrinter(String name) {

this.name = name;

}

//复写run方法

public void run() {

//需要执行的方法

System.out.print(name);

}

}

class ThreadTest{

static public void main(String args[]) {

//创建teat对象,作为参数传入Thread构造函数中,通过Thread实例r启动线程

Test test = new Test("zhang");

Thread r = new Thread(test);

r.start();

}

}


  注意:当使用 runnable 接口时,您不能直接创建所需类的对象并运行它;必须从 Thread 类的一个实例内部运行它。许多程序员更喜欢 runnable 接口,因为从 Thread 类继承会强加类层次。

2、线程的生命周期

当线程被创建并启动,它要经过新建、就绪、运行、阻塞和死亡5中状态。即:

新建(new): 当new一个线程后,该线程处于新建状态,此时它和Java对象一样,仅仅由Java虚拟机为其分配内存空间,并初始化成员变量。此时线程对象没有表现出任何的动态特征,程序也不会执行线程的执行体。

就绪(Keady):线程能够运行,但是在等待可用的处理器。可能刚刚启动,或者刚刚从阻塞中恢复,或者被其它线程抢占。

运行(Kunning):线程正在运行。在多处理器系统中,可能有多个线程处于运行态。

阻塞(Blocked):线程由于等待"处理器"外的其它条件而无法运行,如:条件变量的改变,加锁互质量或者等待I/O操作结束。

终止(Terminated):线程从启动函数中返回,或者调用pthread_exit,或者被取消,终止自己并完成所有资源清理工作。不是被分离,也不是被连接,一旦被分离或者被连接,它就可以被回收。

  下面给出了Thread类中和这四种状态相关的方法:

// 开始线程

publicvoid start( );

publicvoid run( );

// 挂起和唤醒线程

publicstaticvoid sleep(long millis);

publicstaticvoid sleep(long millis, int nanos);

publicvoid resume( ); //少用

publicvoid suspend( );  //少用

// 终止线程

publicvoid stop( );  //少用

publicvoid interrupt( );


  线程在建立后并不马上执行run方法中的代码,而是处于等待状态。线程处于等待状态时,可以通过Thread类的方法来设置线程不各种属性,如线程的优先级(setPriority)、线程名(setName)和线程的类型(setDaemon)等。

当调用start方法后,线程开始执行run方法中的代码。线程进入运行状态。可以通过Thread类的isAlive方法来判断线程是否处于运行状态。当线程处于运行状态时,isAlive返回true,当isAlive返回false时,可能线程处于等待状态,也可能处于停止状态。下面的代码演示了线程的创建、运行和停止三个状态之间的切换,并输出了相应的isAlive返回值。

一但线程开始执行run方法,就会一直到这个run方法执行完成这个线程才退出。但在线程执行的过程中,可以通过两个方法使线程暂时停止执行。这两个方法是suspend和sleep。在使用suspend挂起线程后,可以通过resume方法唤醒线程。而使用sleep使线程休眠后,只能在设定的时间后使线程处于就绪状态(在线程休眠结束后,线程不一定会马上执行,只是进入了就绪状态,等待着系统进行调度)。

3、线程同步

由于系统线程调度具有一定的随机性,造成它很容易出现“错误情况”,这就造成了安全问题。为解决这个问题,jaa多线程引入了同步代码块的概念,具体语法如下:

synchronized (obj){

//此处的代码就是同步代码块

}


  与同步代码块对应的是同步方法,也就是使用synchronized关键字来修饰某个方法,则该方法称为同步方法。通过使用同步方法可以非常方便地实现线程安全的类,具体特征如下:

该类的对象可以被多个线程安全地访问

每个线程调用该对象的任意方法之后都将得到正确结果

每个线程调用该对象的任意方法后,该对象状态依然保持合理状态。

下面是具体的语法:

public void method3(SomeObject so) {

synchronized(so)

{

//…..方法执行体

}

}


  注意:在使用synchronized关键字时候,应该尽可能避免在synchronized方法或synchronized块中使用sleep或者yield方法,因为synchronized程序块占有着对象锁,你休息那么其他的线程只能一边等着你醒来执行完了才能执行。不但严重影响效率,也不合逻辑。

  同样,在同步程序块内调用yeild方法让出CPU资源也没有意义,因为你占用着锁,其他互斥线程还是无法访问同步程序块。当然与同步程序块无关的线程可以获得更多的执行时间。

4、线程通信

线程之间除了同步互斥,还要考虑通信。在Java5之前我们的通信方式为:wait 和 notify。那么Condition的优势是支持多路等待,就是我可以定义多个Condition,每个condition控制线程的一条执行通路。传统方式只能是一路等待。我们可以先分析下Java5 Api中的缓冲队列的实现:

  假定有一个绑定的缓冲区,它支持 put 和 take 方法。如果试图在空的缓冲区上执行take操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行put操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set 中保存put 线程和take 线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个condition实例来做到这一点。

4.1 使用condition控制线程通信

  如果程序不使用synchronized关键字来保证同步,而是直接使用Lock对象来保证同步,则系统中不存在隐式的同步监视器,也就不能使用wait()、notify()、notifyAll()方法进行线程通信了。

  当使用Lock对象来保证同步时,java提供了一个condition类来保持协调,使用condition对象可以让那些已经得到Lock对象却无法继续执行的线程释放Lock对象,condition对象也可以唤醒其他处于等待的线程。

  Condition实例被绑定在一个Lock对象上。要获得特定Lock实例的condition实例,调用Lock对象的newCondition()方法即可。Condition类提供了3种方法:await()、signal()和signalAll()。

4.2 使用阻塞队列(BlockingQueue)控制线程通信

  阻塞队列具有一个特征:当生产者线程试图向BlockingQueue中放入元素时,如果队列已满,则该线程阻塞;当消费者线程试图从BlockingQueue中取出元素时,如果该队列已空,则该线程被阻塞。

  程序的两个线程通过交替向BlockingQueue中放入元素,即可很好地控制线程的通信。BlockingQueue提供了两个支持阻塞的方法:put(E e)和take()。

三、总结

  本文主要简单的介绍了如何创建和启动多线程,并说了两种创建多线程方式之间的优势和劣势,也比较详细的介绍了线程的生命周期,然后简单的说明了线程同步方法,最后介绍了两种实现线程通信的方式,这些都是学习多线程应该掌握的基本知识。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: