继承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类,故没法再继承其余类。任务无返回值。操作系统
/** * 主类 */ 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
//建立任务类,相似于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.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)则是在等待某一事件的发生,常见的如等待其余线程的通知或者中断。
(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调度执行的状况。