【JAVA并发第二篇】Java线程的建立与运行,线程状态与经常使用方法

一、线程的建立与运行

(1)、继承或直接使用Thread类

继承Thread类建立线程:java

/**
 * 主类
 */
public class ThreadTest {
    public static void main(String[] args) {
        //建立线程对象
        My_Thread my_thread = new My_Thread();
        //启动线程
        my_thread.start();
    }
}

/**
 * 继承Thread
 */
class My_Thread extends Thread{
    @Override
    public void run(){  //线程的任务
        System.out.println("My_Thread Running");
    }
}

直接使用Thread类建立线程:windows

class ThreadTest02 {
    public static void main(String[] args) {
        //直接使用Thread建立线程,"My_Thread"是取得线程名
        Thread my_thread = new Thread("My_Thread"){
            @Override
            public void run() {  //线程的任务
                System.out.println("My_Thread Running");
            }
        };
        //启动线程
        my_thread.start();
    }
}

以上的方式都是直接使用Thread类建立线程,并经过start方法启动线程,但线程并不会当即执行,它还须要等待CPU调度,只有线程得到CPU控制权,才算是真正在执行。ide

直接使用Thread类的好处是:
方便传参,可在子类里添加成员变量,经过set方式设置参数或经过构造函数传参函数

直接使用Thread类的缺点处是:
线程的建立和任务代码冗余在一块儿。也可能因为继承了Thread类,故没法再继承其余类。任务无返回值。操作系统

(2)、使用Runnable接口的run方法

/**
 * 主类
 */
public class ThreadTest03 {
    public static void main(String[] args) {
        RunnableTask task = new RunnableTask();
        //建立线程,参数1 是任务对象; 参数2 是线程名字,推荐写上
        Thread my_thread = new Thread(task,"My_Thread");
        //启动线程
        my_thread.start();
    }
}
/**
 * Runable接口实现类
 */
class RunnableTask implements Runnable{
    @Override
    public void run(){  //线程的任务
        System.out.println("Thread Running");
    }
}

以上的方式是使用Runnable接口的run方法,该方式将任务代码与线程的建立分离,这样在多个线程具备相同任务时,就可使用同一个Runnable接口实现,同时该方式的Runnable的实现类也能够继承其余的类。该方式更灵活,故推荐使用其来建立线程。线程

但其缺点也是任务无返回值。code

(3)、使用FutureTask的方式

//建立任务类,相似于Runnable
public class CallerTask implements Callable<String> {
    @Override
    public String call() throws Exception {
        return "hello thread";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //建立任务对象
        FutureTask<String> futureTask = new FutureTask<>(new CallerTask());
        //启动线程
        new Thread(futureTask,"My_Thread").start();
        //主线程等待"My_Thread"的任务执行完毕,并返回结果
        String res = futureTask.get();
        System.out.println(res);
    }
}

上述代码实现了Callable接口的call()方法。在main函数内首先建立FutureTask对象(构造函数为CallerTask的实例)。将建立的FutureTask对象做为任务,并放到新建立的线程中启动。运行完毕后,则可使用get方法等待线程里的任务执行完毕并返回结果。对象

二、Java线程的状态

Java线程在其生命周期中可能有六种状态。根据Java.lang.Thread类中的枚举类型State的定义,其状态有如下六种:blog

①NEW:初始状态,线程已被建立但还未调用start()方法来进行启动。继承

②RUNNABLE:运行状态,调用start方法后,线程处于该状态。注意,Java线程的运行状态,实际上包含了操做系统中的就绪状态(已得到除CPU外的一切运行资源,正在等待CPU调度,得到CPU控制权)和运行状态(得到CPU控制权,线程真正在执行)。所以,即便Java中的线程处于RUNNABLE状态,也并不意味着该线程就必定正在执行(得到CPU的控制权),该线程也有可能在等待CPU调度。

③BLOCKED:阻塞状态,线程阻塞于锁,即线程在锁的竞争中失败,则处于阻塞状态。

④WAITING:等待状态,该状态的线程须要等待其余线程的中断或通知。

⑤TIME-WAITING:超时等待状态,该状态下的线程也在等待通知,但若在限定时间内没有,其余线程进行通知,那么超过规定时间的线程就会自动“醒来”,继续执行run方法内的代码。

⑥TERMINATED:终止状态,线程执行完毕或者线程在执行过程当中抛出异常,则线程结束,线程处于终止状态。

在这里插入图片描述

阻塞状态(BLOCKED),是由于其在锁竞争中失败而在等待得到锁,而等待状态(WAITING)则是在等待某一事件的发生,常见的如等待其余线程的通知或者中断。

三、Java线程Thread类经常使用方法

(1)、start方法

是否为static方法:否。
做用:启动一个新线程,在新线程调用run方法。

说明:线程调用start方法,进入运行状态(RUNNABLE),但并不意味着线程中的代码会当即执行,由于Java线程中的运行状态包含了操做系统层面的【就绪状态】和【运行状态】,因此只有Java线程真正得到了CPU的控制权,线程才能真正地在执行。每一个线程只能调用一次start方法来启动线程,若是屡次调用则会出现IllegalThreadStateException。

(2)、run方法

是否为static方法:否。
做用:线程启动后会调用的方法。

说明:
①若使用继承Thread类的方式建立线程,并重写了run方法,则线程会在启动后调用run方法,执行其中的代码。若是继承时没有重写run方法或者run方法中没有任何代码,则该线程不会进行任何操做。
②若使用实现Runnable接口的方法建立线程,则在调用start启动线程后,也会调用Runnable实现类中的run方法,若是没有重写,则默认不会进行任何操做。

那些run方法和start方法又有什么区别呢?

③start方法是真正能启动一个新线程的方法,而run方法则是线程对象中的普通方法,即便线程没有启动,也能够经过线程对象来调用run方法,run方法并不会启动一个新线程。

代码以下:

public class StartAndRun{
    public static void main(String[] args) {
        //使用Thread建立线程
        Thread t = new Thread("my_thread"){ //为线程命名为"my_thread"
            @Override
            public void run() {
                //Thread.currentThread().getName():获取当前线程的名字
                System.out.println("【"+Thread.currentThread().getName()+"】"+"线程中的run方法被调用");
                for (int i = 0; i < 3; i++) {
                    System.out.println(i);
                }
            }
        };
        //调用run方法
        t.run();
        //调用start方法
        t.start();
    }
}

其结果以下:

【main】线程中的run方法被调用
0
1
2
【my_thread】线程中的run方法被调用
0
1
2

能够看出在my_thread线程启动前(调用start方法前),也能够调用线程对象t中的run方法,调用这个run方法的线程并不会是my_thread线程(由于还没启动呢),而是main方法所在的主线程main。这是由于run方法是做为线程对象的普通方法存在的,能够认为run方法中的代码就是新线程启动后所须要执行的任务。若是经过线程对象调用run方法,那么在哪一个线程调用的run方法,就由哪一个线程负责执行。

总的来讲,Thread类的对象实例对应着操做系统实际存在的一个线程,该对象实例负责提供给用户去操做线程、获取线程信息。start方法会调用native修饰的本地方法start0,最终在操做系统中启动一个线程,并会在本地方法中调用线程对象实例的run方法。因此,调用run方法并不会启动一个线程,它只是做为线程对象等着被调用。

(3)、join方法

是否为static方法:否。
做用:用于同步,可使用该方法让线程之间的并行执行变为串行执行。

有代码以下:

/**
 * 主类
 */
public class Join {
    public static void main(String[] args) throws InterruptedException {
        Task task = new Task();
        Thread t1 = new Thread(task,"耗子尾汁");

        //启动线程
        t1.start();

        //主线程打印
        for(int i = 0; i < 4; i++){
            if (i == 2) {
                //join方法:使main线程与t1线程同步执行,即t1线程执行完,main线程才会继续
                t1.join();  
            }
            //Thread.currentThread().getName():获取当前线程的名称
            System.out.println("【"+Thread.currentThread().getName()+"】" + i); 
        }
    }
}

/**
 * Runnable接口实现类
 */
class Task implements Runnable{
    @Override
    public void run() {
        for(int i = 0; i < 3; i++){
            System.out.println("【"+Thread.currentThread().getName()+"】"+i);
        }
    }
}

其输出以下:

【main】0
【main】1
【耗子尾汁】0
【耗子尾汁】1
【耗子尾汁】2
【耗子尾汁】3
【main】2
【main】3

在上面的代码中,建立了一个命名为“耗子尾汁”的线程,并经过start方法启动。主线程和“耗子尾汁”线程都有循环打印i的任务。在“耗子尾汁”线程启动后,就会进入运行状态(Runnable),等待CPU调度,以得到CPU使用权来打印i。而主线程在执行“耗子尾汁”线程的start方法后,就会继续往下执行,循环打印i。正常来说,主线程和“耗子尾汁”线程应该处于并行执行的状态,即两者会各自执行本身的for循环。但因为在主线程的for循环中调用了join方法,使得主线程交出了CPU的控制权,并返回到“耗子尾汁”线程,等待该线程执行完毕,主线程才继续执行。因此join方法就至关于在主线程中同步“耗子尾汁”线程,使“耗子尾汁”线程执行完,才会继续执行主线程。其最终效果就是可使用该方法让线程之间的并行执行变为串行执行。

join方法是能够传参的。join(10)的意思就是,若是在A线程中调用了B线程.join(10),那么A线程就会同步等待B线程10毫秒,10毫秒后,A、B线程就会并行执行。

同时也要注意,只有线程启动了,调用join方法才有意义。在上述代码中,若是“耗子尾汁”线程没有调用start方法来启动,那么join并不会起做用。

(4)、getId方法、getName方法、setName方法
是否为static方法:均为否。
做用:
①getId方法:获取线程长整型的id、这个线程id是惟一的。
②getName方法:获取线程名
③setName(String):设置线程名

(5)、getPriority方法、setPriority(int)方法
是否为static方法:均为否。
做用:
①setPriority(int)方法:设置线程的优先级,优先级的范围为1-10。
②getPriority方法:获取线程的优先级。

如今的主流操做系统(windows、Linux等)基本都采用了时分的形式来调度运行线程,即将CPU的时间分为一个个时间片(这些时间片相等的),线程会获得若干时间片,时间片用完就会发生线程调度,并等待下一次的分配。线程优先级就是决定线程须要多或者少分配一些时间片。

Java线程的优先级范围为1-10,默认优先级为5。优先级高的线程分配的时间片的数量要都多于优先级低的线程。可经过setPriority(int)方法来设置。频繁阻塞的线程(好比I/O操做或休眠)的线程须要设置较高优先级,而计算任务较重(好比偏向运算操做或须要较多CPU时间)的线程则设置较低优先级,以免CPU会被独占。

须要注意的是,Java线程的优先级设置只能给操做系统建议,并不能直接决定线程的调度,Java线程的调度只能由操做系统决定。操做系统彻底能够忽略Java线程的优先级设置。在不一样的操做系统上Java线程的优先级会存在差别,一些操做系统会直接无视优先级的设置。因此一些在逻辑上有前后顺序的操做,不能依靠设置Java线程的优先级来完成。

Java子线程的默认优先级与父线程的优先级一致,例如在main方法中建立线程,那么主线程(默认为5)就是这个新线程的父线程,该新线程的默认优先级为父线程的优先级。若是给主线程设置优先级为4,那么这个新线程的默认优先级就为4。

(6)、getState()方法、isAlive()方法
是否为static方法:均为否。
做用:
①getState()方法:获取线程的状态(NEW、RUNNABLE、WATING、BLOCKED、TIME_WATING、TERMINATED)
②isAlive()方法:判断线程是否存活,便是否线程已启动但还没有终止((尚未运行完
毕))。

(7)、interrupt()方法
是否为static方法:否。
做用:中断线程,当A线程运行时,B线程能够经过A线程的对象实例来调用A线程的interrput()方法设置线程A的中断标志位true,并当即返回。设置中断仅仅是设置标志,经过设置中断标志并不能直接终止该线程的执行,而是被中断的线程根据中断状态自行处理。若是打断的是正在运行中的线程,那么该线程就会被设置中断标志。但若是线程正在执行sleep方法或者上面所说的join方法时,被调用了interrupt方法,那么这个被打断的线程会抛出出 InterruptedException异常,并清除打断标志。

(8)、interrupted()方法、isInterrupted()方法
是否为static方法:interrupted为非static方法、isInterrupted为static方法
做用:均为判断线程是否被打断。区别在于interrupted()方法不会清除中断标记,isInterrupted()方法会清除中断标志。

(9)、sleep(long n)方法
是否为static方法:是。
做用:让线程休眠,当一个执行中的线程调用sleep方法后,该线程就会挂起,并把剩下的CPU时间片交给其余线程,但并不会直接指定由哪一个线程占用,须要操做系统来进行调度。线程在休眠期间不参与CPU调度,但也不会把线程占有的其余资源(好比锁)进行释放。

须要注意的是,休眠时间到后线程也并不会直接继续执行,而是进入等待CPU调度的状态。同时因为sleep方法是静态方法,使用t.sleep()并不会让t线程进入休眠,而是让当前线程进入休眠(好比在main方法中调用t.sleep(),其实是让主线程进入休眠)。

(10)、yield() 方法 是否为static方法:是。 做用:使线程让出CPU控制权。实际上该方法只是向操做系统请求让出本身的CPU控制权,但操做系统也能够选择忽略。线程调用该方法让出CPU控制权后,会进入就绪状态,也有可能遇到刚让出CPU控制权后又被CPU调度执行的状况。

相关文章
相关标签/搜索