Java 线程Thread

Ø线程的概述(Introduction)

线程是一个程序的多个执行路径,执行调度的单位,依托于进程存在。 线程不仅可以共享进程的内存,而且还拥有一个属于自己的内存空间,这段内存空间也叫做线程栈,是在建立线程时由系统分配的,主要用来保存线程内部所使用的数据,如线程执行函数中所定义的变量。

注意:Java中的多线程是一种抢占机制而不是分时机制。抢占机制指的是有多个线程处于可运行状态,但是只允许一个线程在运行,他们通过竞争的方式抢占CPU。

 

Ø线程的使用(Defining)

  定义一个线程(Defining a Thread)有两种方法

  1) 继承java.lang.Thread类

复制代码
/**
 * 使用继承java.lang.Thread类的方式创建一个线程
 * 
 * @author DreamSea 2011-12-29 20:17:06
 */
public class ThreadTest extends Thread {

    /**
     * 重写(Override)run()方法 JVM会自动调用该方法
     */
    public void run() {
        System.out.println("I'm running!");
    }
}
复制代码

  注意:重写(override)run()方法在该线程的start()方法被调用后,JVM会自动调用run方法来执行任务;但是重载(overload)run()方法,该方法和普通的成员方法一样,并不会因调用该线程的start()方法而被JVM自动运行。 例如:

public class ThreadTest extends Thread {

    /**
     * 重写(Override)run()方法 JVM会自动调用该方法
     */
    @Override
    public void run() {
        System.out.println("I'm running!");
    }

    /**
     * 重载(Overload)run()方法 和普通的方法一样,并不会在该线程的start()方法被调用后被JVM自动运行
     */
    public void run(int times) {
        System.out.println("I'm running!(Overload)");
    }
}

不建议使用此方法定义线程,因为采用继承Thread的方式定义线程后,你不能在继承其他的类了,导致程序的可扩展性大大降低。

 

  2) 实现java.lang.Runnable接口

/*
 * 通过实现Runnable接口创建一个线程
 * @author DreamSea
 */
public class ThreadTest implements Runnable {
    public void run() {
            System.out.println("I'm running!");
    }
}

 

Ø线程的启动(Starting)

  线程在建立后并不马上执行run方法中的代码,而是处于等待状态。任何一个线程的执行的前提都是必须有Thread class的实例存在,并且通过调用run()方法启动线程。

  1)如果线程是继承Thread类,则创建方式如下: 

ThreadTest1 tt = new ThreadTest1();
tt.start();

 

  2)如果是实现Runnable接口,则创建方式如下:

ThreadTest2 tt = new ThreadTest2();
Thread t = new Thread(tt);
t.start();

 

Ø线程的状态(State)

  新生状态(New): 当一个线程的实例被创建即使用new关键字和Thread类或其子类创建一个线程对象后,此时该线程处于新生(new)状态,处于新生状态的线程有自己的内存空间,但该线程并没有运行,此时线程还不是活着的(not alive);

  就绪状态(Runnable): 通过调用线程实例的start()方法来启动线程使线程进入就绪状态(runnable);处于就绪状态的线程已经具备了运行条件,但还没有被分配到CPU即不一定会被立即执行,此时处于线程就绪队列,等待系统为其分配CPCU,等待状态并不是执行状态; 此时线程是活着的(alive);

  运行状态(Running): 一旦获取CPU(被JVM选中),线程就进入运行(running)状态,线程的run()方法才开始被执行;在运行状态的线程执行自己的run()方法中的操作,直到调用其他的方法而终止、或者等待某种资源而阻塞、或者完成任务而死亡;如果在给定的时间片内没有执行结束,就会被系统给换下来回到线程的等待状态;此时线程是活着的(alive);

  阻塞状态(Blocked):通过调用join()、sleep()、wait()或者资源被暂用使线程处于阻塞(blocked)状态;处于Blocking状态的线程仍然是活着的(alive)

  死亡状态(Dead):当一个线程的run()方法运行完毕或被中断或被异常退出,该线程到达死亡(dead)状态。此时可能仍然存在一个该Thread的实例对象,当该Thready已经不可能在被作为一个可被独立执行的线程对待了,线程的独立的call stack已经被dissolved。一旦某一线程进入Dead状态,他就再也不能进入一个独立线程的生命周期了。对于一个处于Dead状态的线程调用start()方法,会出现一个运行期(runtime exception)的异常;处于Dead状态的线程不是活着的(not alive)。

 线程状态图

 线程状态图

Ø线程的方法(Method)、属性(Property)

1)优先级(priority)

每个类都有自己的优先级,一般property用1-10的整数表示,默认优先级是5,优先级最高是10;优先级高的线程并不一定比优先级低的线程执行的机会高,只是执行的机率高;默认一个线程的优先级和创建他的线程优先级相同;

2)Thread.sleep()/sleep(long millis)

当前线程睡眠/millis的时间(millis指定睡眠时间是其最小的不执行时间,因为sleep(millis)休眠到达后,无法保证会被JVM立即调度);sleep()是一个静态方法(static method) ,所以他不会停止其他的线程也处于休眠状态;线程sleep()时不会失去拥有的对象锁。 作用:保持对象锁,让出CPU,调用目的是不让当前线程独自霸占该进程所获取的CPU资源,以留一定的时间给其他线程执行的机会;

3)Thread.yield()

  让出CPU的使用权,给其他线程执行机会、让同等优先权的线程运行(但并不保证当前线程会被JVM再次调度、使该线程重新进入Running状态),如果没有同等优先权的线程,那么yield()方法将不会起作用。

4)thread.join()

 使用该方法的线程会在此之间执行完毕后再往下继续执行。

5)object.wait()

  当一个线程执行到wait()方法时,他就进入到一个和该对象相关的等待池(Waiting Pool)中,同时失去了对象的机锁—暂时的,wait后还要返还对象锁。当前线程必须拥有当前对象的锁,如果当前线程不是此锁的拥有者,会抛出IllegalMonitorStateException异常,所以wait()必须在synchronized block中调用。

6)object.notify()/notifyAll()

  唤醒在当前对象等待池中等待的第一个线程/所有线程。notify()/notifyAll()也必须拥有相同对象锁,否则也会抛出IllegalMonitorStateException异常。

7)Synchronizing Block

  Synchronized Block/方法控制对类成员变量的访问;Java中的每一个对象都有唯一的一个内置的锁,每个Synchronized Block/方法只有持有调用该方法被锁定对象的锁才可以访问,否则所属线程阻塞;机锁具有独占性、一旦被一个Thread持有,其他的Thread就不能再拥有(不能访问其他同步方法),方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获得该锁,重新进入可执行状态。

Thread类中和这四种状态相关的方法:
// 开始线程
    public void start( );
    public void run( );

    
// 挂起和唤醒线程
    public void resume( );     // 不建议使用
    public void suspend( );    // 不建议使用
    public static void sleep(long millis);
    public static void sleep(long millis, int nanos);

    // 终止线程
    public void stop( );       // 不建议使用
    public void interrupt( );

    // 得到线程状态
    public boolean isAlive( );
    
public boolean isInterrupted( );
    public static boolean interrupted( );

    // join方法
    public void join( ) throws InterruptedException;

挂起和唤醒线程:


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

    虽然suspend和resume可以很方便地使线程挂起和唤醒,但由于使用这两个方法可能会造成一些不可预料的事情发生,因此,这两个方法被标识为deprecated(抗议)标记,这表明在以后的jdk版本中这两个方法可能被删除,所以尽量不要使用这两个方法来操作线程。


使用sleep和suspend所产生的效果类似,但sleep方法并不等同于suspend.它们之间最大的一个区别是可以在一个线程中通过suspend方法来挂起另外一个线程,如上面代码中在主线程中挂起了thread线程。而sleep只对当前正在执行的线程起作用。在上面代码中分别使sleepThread和主线程休眠了2秒和5秒。在使用sleep时要注意,不能在一个线程中来休眠另一个线程。如main方法中使用thread.sleep(2000)方法是无法使thread线程休眠2秒的,而只能使主线程休眠2秒。

    在使用sleep方法时有两点需要注意:

    1. sleep方法有两个重载形式,其中一个重载形式不仅可以设毫秒,而且还可以设纳秒(1,000,000纳秒等于1毫秒)。但大多数操作系统平台上的Java虚拟机都无法精确到纳秒,因此,如果对sleep设置了纳秒,Java虚拟机将取最接近这个值的毫秒。

    2. 在使用sleep方法时必须使用throws或try{……}catch{……}.因为run方法无法使用throws,所以只能使用try{……}catch{……}.当在线程休眠的过程中,使用interrupt方法(这个方法将在2.3.3中讨论)中断线程时sleep会抛出一个InterruptedException异常。sleep方法的定义如下:

public static void sleep(long millis)  throws InterruptedException
public static void sleep(long millis,  int nanos)  throws InterruptedException

终止线程的三种方法:

    有三种方法可以使终止线程。

    1.  使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。

    2.  使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。

    3.  使用interrupt方法中断线程。

    1. 使用退出标志终止线程

    当run方法执行完后,线程就会退出。但有时run方法是永远不会结束的。如在服务端程序中使用线程进行监听客户端请求,或是其他的需要循环处理的任务。在这种情况下,一般是将这些任务放在一个循环中,如while循环。如果想让循环永远运行下去,可以使用while(true){……}来处理。但要想使while循环在某一特定条件下退出,最直接的方法就是设一个boolean类型的标志,并通过设置这个标志为true或false来控制while循环是否退出。

使用控制位:

public class Task extends Thread {
    private volatile boolean flag= true;
    
    public void stopTask() {
        flag = false;
    }
    @Override
    public void run() {
        while(flag){
            /* do your no-block task */
        }
    }
}


使用stop方法终止线程

    使用stop方法可以强行终止正在运行或挂起的线程。我们可以使用如下的代码来终止线程:

thread.stop();

    虽然使用上面的代码可以终止线程,但使用stop方法是很危险的,就象突然关闭计算机电源,而不是按正常程序关机一样,可能会产生不可预料的结果,因此,并不推荐使用stop方法来终止线程


使用interrupt方法终止线程:

    

当线程等待某些事件发生而被阻塞,又会发生什么?当然,如果线程被阻塞,它便不能核查共享变量,也就不能停止。这在许多情况下会发生,例如调用 Object.wait()、Thread.sleep等,这里仅举出一些。他们都可能永久的阻塞线程。即使发生超时,在超时期满之前持续等待也是不可行和不适当的,所以,要使用某种机制使得线程更早地退出被阻塞的状态。这个时候你可以使用Thread.interrupt();

使用interrupt方法来终端线程可分为两种情况

    (1)线程处于阻塞状态,如使用了sleep方法。

    (2)使用while(!isInterrupted()){……}来判断线程是否被中断。

    在第一种情况下使用interrupt方法,sleep方法将抛出一个InterruptedException例外,而在第二种情况下线程将直接退出。下面的代码演示了在第一种情况下使用interrupt方法。


在调用interrupt方法后, sleep方法抛出异常,然后输出错误信息:sleep interrupted.

    注意:在Thread类中有两个方法可以判断线程是否通过interrupt方法被终止。一个是静态的方法interrupted(),一个是非静态的方法isInterrupted(),这两个方法的区别是interrupted用来判断当前线是否被中断,而isInterrupted可以用来判断其他线程是否被中断。因此,while (!isInterrupted())也可以换成while (!Thread.interrupted())。

public class BlockTask extends Thread {
    @Override
    public void run() {
        try {
            while (!Thread.interrupted()) {
                /* do your block task*/    
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        
    }
}
但是上面的代码或许有些不妥,或许用例子更能把问题说清楚。你怎么知道该代码段会发生阻塞?interrupt()函数到底是什么意思呢?首先说明的是,interrupted()方法只能解决跑出InterruptedException异常的阻塞。而interrupt()并不是关闭阻塞线程,而且解除阻塞。那这里就举出一个关闭线程阻塞的例子把:
public class BlockTask extends Thread {
    @Override
    public void run() {
        try {
            sleep(10000);
        } catch (InterruptedException e) {
            System.out.println("if yout use interrupt you will see me");
        }
        
    }
    public static void main(String[] args)throws Exception {
        // TODO Auto-generated method stub
        BlockTask task = new BlockTask();
        task.start();
        Thread.sleep(1000);
        task.interrupt();
    }
}
interrupt()只能解决InterruptedException的阻塞的线程,那么遇到一些其他的io阻塞怎么处理呢?这个时候java都会提供相应的关闭阻塞的办法。例如,服务器可能需要等待一个请求(request),又或者,一个网络应用程序可能要等待远端主机的响应,这个时候可以使用套接字close()方法:
public class SocketTask extends Thread {
    private volatile ServerSocket server;
    
    public void stopTask(){
        try {
            if(server!=null){
                server.close();
                System.out.println("close task successed");
            }
        } catch (IOException e) {
            System.out.println("close task failded");
        }
    }
    @Override
    public void run() {
        try {
            server = new ServerSocket(3333);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        
        SocketTask task = new SocketTask();
        task.start();
        Thread.sleep(1000);
        task.stopTask();
    }


}

public class AlternateStop extends Object implements Runnable {
    private volatile boolean stopRequested;
    private Thread runThread;
    public void run() {
        runThread = Thread.currentThread();
        stopRequested = false;
        int count = 0;
        while ( !stopRequested ) {
            System.out.println("Running ... count=" + count);
            count++;
            try {
                Thread.sleep(300);
            } catch ( InterruptedException x ) {
                Thread.currentThread().interrupt(); // re-assert interrupt
            }
        }
        
        System.out.println("stoped");
    }
    public void stopRequest() {
        stopRequested = true;
        if ( runThread != null ) {
            runThread.interrupt();
        }
    }
    public static void main(String[] args) {
        AlternateStop as = new AlternateStop();
        Thread t = new Thread(as);
        t.start();
        try {
            Thread.sleep(2000);
        } catch ( InterruptedException x ) {
            // ignore
        }
        as.stopRequest();
    }
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值