1、线程的创建与运行

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

继承Thread类创建线程:

/**
* 主类
*/
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类创建线程:

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控制权,才算是真正在执行。

直接使用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的实现类也可以继承其他的类。该方式更灵活,故推荐使用其来创建线程。

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

(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方法等待线程里的任务执行完毕并返回结果。

2、Java线程的状态

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

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

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

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

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

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

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

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

3、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调度执行的情况。

【JAVA并发第二篇】Java线程的创建与运行,线程状态与常用方法的更多相关文章

  1. 8成以上的java线程状态图都画错了,看看这个-图解java并发第二篇

    本文作为图解java并发编程的第二篇,前一篇访问地址如下所示: 图解进程线程.互斥锁与信号量-看完还不懂你来打我 图形说明 在开始想写这篇文章之前,我去网上搜索了很多关于线程状态转换的图,我惊讶的发现 ...

  2. Java SE 第二篇

    二.  Java SE 第二篇 1.  Arrays 数组 // 声明一维数组,[]内不允许有值 int[] arr; int arr[]; // 创建一维数组对象,[]内必须有值 arr = new ...

  3. Java并发编程:Java的四种线程池的使用,以及自定义线程工厂

    目录 引言 四种线程池 newCachedThreadPool:可缓存的线程池 newFixedThreadPool:定长线程池 newSingleThreadExecutor:单线程线程池 newS ...

  4. “全栈2019”Java多线程第二十五章:生产者与消费者线程详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  5. Java并发包下锁学习第二篇Java并发基础框架-队列同步器介绍

    Java并发包下锁学习第二篇队列同步器 还记得在第一篇文章中,讲到的locks包下的类结果图吗?如下图: ​ 从图中,我们可以看到AbstractQueuedSynchronizer这个类很重要(在本 ...

  6. Java并发编程(二)-- 创建、运行线程

    Java线程 Java线程类也是一个object类,它的实例都继承自java.lang.Thread或其子类. Java可以用如下方式创建一个线程: Tread thread = new Thread ...

  7. 7、Java并发性和多线程-如何创建并运行线程

    以下内容转自http://ifeve.com/creating-and-starting-java-threads/: Java线程类也是一个object类,它的实例都继承自java.lang.Thr ...

  8. 【JAVA并发第一篇】Java的进程与线程

    1.进程与线程 1.1.进程 进程可以看作是程序的执行过程.一个程序的运行需要CPU时间.内存空间.文件以及I/O等资源.操作系统就是以进程为单位来分配这些资源的,所以说进程是分配资源的基本单位. ( ...

  9. Java并发编程:Java创建线程的三种方式

    目录 引言 创建线程的三种方式 一.继承Thread类 二.实现Runnable接口 三.使用Callable和Future创建线程 三种方式的对比 引言 在日常开发工作中,多线程开发可以说是必备技能 ...

随机推荐

  1. 基础知识redis详解--【Foam番茄】

    Redis 学习方式: 上手就用 基本的理论先学习,然后将知识融汇贯通 nosql讲解 为什么要用Nosql 现在都是大数据时代 大数据一般的数据库无法进行分析处理了 至少要会Springboot+S ...

  2. 第一次UML编程作业

    博客班级 https://edu.cnblogs.com/campus/fzzcxy/2018SE2/ 作业要求 https://edu.cnblogs.com/campus/fzzcxy/2018S ...

  3. 为什么SimpleDateFormat不是线程安全的?

    一.前言 日期的转换与格式化在项目中应该是比较常用的了,最近同事小刚出去面试实在是没想到被 SimpleDateFormat 给摆了一道... 面试官:项目中的日期转换怎么用的?SimpleDateF ...

  4. 【2020.11.30提高组模拟】删边(delete)

    删边(delete) 题目 题目描述 给你一棵n个结点的树,每个结点有一个权值,删除一条边的费用为该边连接的两个子树中结点权值最大值之和.现要删除树中的所有边,删除边的顺序可以任意设定,请计算出所有方 ...

  5. JZOJ 2020.10.7 提高B组反思

    JZOJ 2020.10.7 提高B组反思 T1 比较简单的一道题 跑\(k\)遍\(SPFA\) 然后全排列顺序枚举求解 TLE 60 双向存边数组没开两倍-- T2 搞出分母 分子不会求 \(n^ ...

  6. Netty 心跳处理

    传统的心跳包设计,基本上是服务端和客户端同时维护 Scheduler,然后双方互相接收心跳包信息,然后重置双方的上下线状态表.此种心跳方式的设计,可以选择在主线程上进行,也可以选择在心跳线程中进行,由 ...

  7. MAC下go语言的安装和配置

    Mac下安装一些文件都是比较简单的.安装了brew以后,很多的程序只要一条命令就搞定了. brew install go 安装好go语言以后主要是配置go_path,和go_root的地址. go_r ...

  8. 强大的拉姆表达式转Sql 类库 - SqlSugar 隐藏功能之Lambda

    使用场景 1.Lambda to sql 一直是ORM中最难的功能之一,如果有现成的解析库那么自已写一个ORM难度将大大降低 2.通过Lambda作为KEY进行缓存操作,特别是仓储模式想要拿到表达式进 ...

  9. 关于java链接装载的思考

    遇到个bug,noClassFoundEx,很常见. 但是问题来了. 比如我的服务器目录是 /opt/tomcat/webapps/ROOT/WEB-INF/classes/cn/controller ...

  10. Python中高级知识(非专题部分)学习随笔

    Python学习随笔:使用xlwings读取和操作Execl文件 Python学习随笔:使用xlwings新建Execl文件和sheet的方法 博客地址:https://blog.csdn.net/L ...