Java中Thread类详解及常用的方法

目录
  • 一、Thread 的常见构造方法
  • 二、Thread 的常见属性
  • 三、创建线程
  • 四、中断线程
  • 五、线程等待
  • 六、获取线程引用
  • 七、线程休眠
  • 八、线程状态
  • 总结

一、Thread 的常见构造方法

方法 说明
Thread() 创建线程对象
Thread(Runnable target) 使用 Runnable 对象创建线程对象
Thread(String name) 创建线程对象并命名
Thread(Runnable target,String name) 使用 Runnable 对象创建线程对象并命名

关于前两种方法,在之前的线程创建介绍中有使用到

线程创建根本上来讲有两种创建方法:

创建一个继承自 Thread 类的子类,重写 Thread 中的 run 方法,调用 start 方法创建一个实现 Runnable 接口的类,重写 Thread 中的 run 方法。创建 Thread 实例,将自己写的实现 Runnable 接口的类的实例设置进去,调用 start 方法

构造方法三和四不过是在前面两种构造方法的基础上多添加了一个给线程对象命名的参数,方便程序员进行调试。

代码(以构造方法四为例):

public class func7 {
    public static void main(String[] args) {
        Thread thread = new Thread(() ->{
            while (true) {
                System.out.println("This is my Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"myThread");
        //此处用 lambda 表达式代替 Runnable 实例,更加简洁,添加了一个参数指定thread线程的名字
        thread.start();
        while (true) {
            System.out.println("my main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

程序员可以通过 JDK 自带的jconsole工具来直观查看这里创建的线程

步骤一:

运行程序后,找到自己的 jdk 路径 -> bin ->jconsole.exe

步骤二:双击该 exe 文件。选择本地进程,这里罗列着 Java 进程。可以看见我们的程序 func7 ,点击它。在菜单栏选择线程一栏

步骤三:查看线程信息

左侧可以选择需要被查看的线程,可以看见主线程 main和新创建的线程 myThread ,如果没有重命名操作的话新创建的线程名就会叫 Thread-0,Thread-1 这样的名字,不方便查看。

右侧显示了该线程的被读取的那一瞬间的状态,堆栈跟踪显示的是代码具体执行的位置

二、Thread 的常见属性

属性 方法
ID getId()
名称 getName()
状态 getState()
优先级 getPriority()
是否为后台线程 isDaemon()
是否存活 isAlive()
是否被中断 isInterrupted()

解释:

  1. 线程的唯一标识就是线程 Id
  2. 名称在上面的案例中有所体现,为调试提供了便利
  3. 状态表示线程当前所处的情况,上面的案例中,线程因为调用了 sleep 方法就进入了阻塞状态
  4. 优先级表示该线程被调度到的难易度,高的更容易被调度到
  5. 判断是否为后台线程。如果是后台线程,那么该后台线程不会影响 java 进程的结束;如果是非后台线程,JVM 就会等到所有的非后台线程执行完毕,才会结束运行,因此会影响到总进程的结束
  6. 是否存活是来判断线程是否还存在的方法。当创建出 Thread 实例对象时,线程未必就创建了,需要调用 start 方法,才是真正的创建了线程。当线程中的 run 方法执行完毕,线程结束被销毁了,创建出的实例对象还没被销毁回收。所以说,创建出的实例对象和线程的生命周期是不完全相同的

在线程的状态中,除了NEW 和 TERMINATED 以外的状态都是活着的

代码:

public class func8 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            for (int i = 0;i < 5;i ++) {
                System.out.println("新线程~");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"newThread");
        System.out.println("新线程状态:" +t.getState());
        //创建了对象,没创建线程
        t.start();
        System.out.println("新线程状态:" +t.getState());
        //创建了线程
        System.out.println("新线程Id:"+t.getId());
        System.out.println("新线程名称:"+t.getName());
        System.out.println("新线程是否为后台线程:" + t.isDaemon());
        System.out.println("新线程是否被中断:" + t.isInterrupted());
        System.out.println("新线程优先级:" + t.getPriority());
        System.out.println("主线程名称:"+Thread.currentThread().getName());
        while (t.isAlive()) {} //当t线程还存在时,主线程就搁这儿循环着,直到线程结束
        System.out.println("新线程状态:" +t.getState());//线程结束
    }
}

结果:

三、创建线程

创建 Thread 类的对象不意味着线程被创建出,start() 方法才是真正的在操作系统内部创建一个新的线程,通过重写 run 方法来描述需要执行的任务,从而真正实现了多线程运行。

四、中断线程

方法一:手动设置标志位,作为中断线程的条件

public class func9 {
    private static Boolean flag = false;//手动设置的标志位 flag
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while (!flag) {
                //flag 为真时停止循环
                System.out.println("myThread");
                try {
                    Thread.sleep(1000);//打印一次,阻塞一秒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();//创建了线程 t
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        flag = true;
        //等3秒后,在主线程中将 flag 的值改成 true,从而使线程t循环条件不成立
    }
}

方法二:使用 Thread 实例中的标志位

public class func10 {
    public static void main(String[] args) {
        Thread t = new Thread() {
            @Override
            public void run() {
                //通过 isInerrupted()判断标志位是否为true,为true说明线程要退出
                while (!this.isInterrupted()) {
                   System.out.println("my Thread");
                   try {
                       Thread.sleep(1000);
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                       //System.out.println("完善工作");
                       //break;
                   }
               }
            }
        };
        t.start();//创建新的线程
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.interrupt();
        //t线程运行3秒后,通过 interrupt() 方法将标志位设置为 true
    }
}

结果:

可以看见当3秒后标志位设置为 true,希望 t 线程中断退出时,结果只是报了个 InterruptedException 异常。

事实上,当调用 interrupt 方法时,如果线程为就绪状态,就会直接修改线程中的标志位;如果是阻塞状态,就会引起 InterruptedException 异常(因为调用了 sleep 方法,正阻塞着呢,结果被强行呼醒了)

但收到中断线程这个信号后,出现了异常,只是单纯的打印了一下异常,并没有对出现的异常进行反应,于是 t 线程就继续循环,就好像中断线程这个信号只是提醒了一下,线程打个日志就完事,当做没听见。

事实上,这样的机制是有存在的道理的,如果调用了 interrupt 方法后,线程说中断就中断,是非常不符合常理的,此时并不知道线程执行到什么地方,收到中断的信号后,怎么说也要进行收尾工作,由线程自己决定什么时候被销毁

因此我们要在捕捉到 InterruptedException 异常后,进行工作完善,然后通过 break 跳出循环,结束线程

方法二和方法一相比更加好。因为方法一中的中断标志被修改后,即使当时正在 sleep ,也会把当下的 sleep 的时间过完,才会进行下一轮判断,才知道线程被中断。方法二即使是在 sleep,收到中断信号后,就会马上被唤醒,中断信息收到的更加的及时。

结果:

五、线程等待

通过 join() 方法来决定线程执行顺序(主要控制结束线程的顺序)。

public class func11 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
           for (int i = 0;i < 3;i ++) {
               System.out.println("my Thread~~");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        t1.start();
        //t1.join();
        for (int i = 0;i < 3;i ++) {
            System.out.println("my main!!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

结果:

在没有调用 join 方法时,main 线程和 t1 线程是并发的,结果输出是相间的。调用 join 方法后,main 线程就会阻塞等待,要等 t1 线程执行完毕,才会执行 join 方法后的内容

不带参数的 join 方法,等待结束的条件就是 t1 线程结束,没结束就会一直死等;该方法也可以带参数,通过参数来指定等待时间

六、获取线程引用

在线程代码中,需要获取当前线程对应的 Thread 类的实例化对象,才能进行更多的操作

方法一:通过继承 Thread 类创建的线程,可以在重写的 run 方法中通过 this 获取当前线程的实例

在上面的中断线程中的方法二中就是通过 this.isInterrupted() 来获取当前实例是否被中断的信息。

如果将创建线程的方式改成创建 Runnable 实例的方法,当前的run 方法就不是 Thread 类的方法,this 指向的是 Runnable,就没有办法获取 Thread 实例,更没有办法使用其中的方法

方法二:通过 Thread 类的 currentThread() 方法,哪个线程调用了该方法,就返回哪个线程的实例对象

七、线程休眠

该方法在前面经常介绍,那就是 sleep 方法

一旦调用 sleep 方法,线程就会阻塞等待,等待的时间取决于指定的参数

操作系统是以线程为单位进行调度的,每个线程都对应着一个 PCB,并通过双向链表组织这些 PCB

操作系统调度 PCB 时,就是从就绪队列中选出一个 PCB 去 CPU 上执行,当执行着的线程调用了 sleep 方法,这个 PCB 就会被移动到阻塞队列中,等到 sleep 的时间到了,就会回到就绪队列中,准备好被执行

join 方法也会产生阻塞等待,就像线程等待中的例子,main 线程执行到 join方法后,就到阻塞队列中,等待对应的 t1 线程执行完毕,才会回到就绪队列中,做好被执行的准备

八、线程状态

从之前的 Thread 的常见属性这里的代码案例可以看出来线程的状态不只是就绪和阻塞,即使是阻塞也分成好几种阻塞类型

//线程的状态是一个枚举类型 Thread.State
//打印 Java 线程中的所有状态
public class func13 {
    public static void main(String[] args) {
        for (Thread.State state : Thread.State.values()) {
            System.out.println(state);
        }
    }
}

结果:

  • NEW:表示 Thread 类的对象创建出,但是线程还没有被创建,即没有调用 start 方法
  • RUNNABLE:就绪状态
  • BLOCKED:等待锁时的状态(期待下一篇关于线程安全的博客)
  • WAITING:通过 wait 方法触发(期待之后的博客)
  • TIMED_WAITING:通过 sleep 方法产生
  • TERMINATED:线程已经执行完毕,但 Thread 类的对象还存在,未被销毁

总结

到此这篇关于Java中Thread类详解及常用的方法的文章就介绍到这了,更多相关Java Thread类方法内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • java线程之用Thread类创建线程的方法

    在Java中创建线程有两种方法:使用Thread类和使用Runnable接口.在使用Runnable接口时需要建立一个Thread实例.因此,无论是通过Thread类还是Runnable接口建立线程,都必须建立Thread类或它的子类的实例.Thread类的构造方法被重载了八次,构造方法如下: 复制代码 代码如下: public Thread( ); public Thread(Runnable target); public Thread(String name); public Thread

  • java实现多线程的两种方式继承Thread类和实现Runnable接口的方法

    实现方式和继承方式有什么区别呢? *区别: *继承Thread:线程代码存放在Thread子类run方法中 *实现Runnable:线程代码存放在接口的子类的run方法中 *实现方式的好处:避免了单继承的局限性 *在定义线程时,建议使用实现方式,当然如果一个类没有继承父类,那么也可以通过继承Thread类来实现多线程 *注意:Runnable接口没有抛出异常,那么实现它的类只能是try-catch不能throws *Java对多线程的安全问题提供了专业的解决方式就是同步代码块synchroniz

  • Java中Thread类详解及常用的方法

    目录 一.Thread 的常见构造方法 二.Thread 的常见属性 三.创建线程 四.中断线程 五.线程等待 六.获取线程引用 七.线程休眠 八.线程状态 总结 一.Thread 的常见构造方法 方法 说明 Thread() 创建线程对象 Thread(Runnable target) 使用 Runnable 对象创建线程对象 Thread(String name) 创建线程对象并命名 Thread(Runnable target,String name) 使用 Runnable 对象创建线程

  • Java 中ThreadLocal类详解

    ThreadLocal类,代表一个线程局部变量,通过把数据放在ThreadLocal中,可以让每个线程创建一个该变量的副本.也可以看成是线程同步的另一种方式吧,通过为每个线程创建一个变量的线程本地副本,从而避免并发线程同时读写同一个变量资源时的冲突. 示例如下: import java.util.Random; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import ja

  • Java中final关键字详解

    谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来了解final这个关键字的用法. 主要介绍:一.final关键字的基本用法.二.深入理解final关键字 一.final关键字的基本用法 在Java中,final关键字可以用来修饰类.方法和变量(包括成员变量和局部变量).下面就从这三个方面来了解一下final关键字的基本用法. 1.修饰类 当用final修饰一个类时,表明这个类不能

  • Java中Volatile关键字详解及代码示例

    一.基本概念 先补充一下概念:Java内存模型中的可见性.原子性和有序性. 可见性: 可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉.通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值,有时甚至是根本不可能的事情.为了确保多个线程之间对内存写入操作的可见性,必须使用同步机制. 可见性,是指线程之间的可见性,一个线程修改的状态对另一个线程是可见的.也就是一个线程修改的结果.另一个线程马上就能看到.比如:用volatile修饰的变量,就会具有可见性.volatile修饰的

  • java中staticclass静态类详解

    一般情况下是不可以用static修饰类的.如果一定要用static修饰类的话,通常static修饰的是匿名内部类. 在一个类中创建另外一个类,叫做成员内部类.这个成员内部类可以静态的(利用static关键字修饰),也可以是非静态的.由于静态的内部类在定义.使用的时候会有种种的限制.所以在实际工作中用到的并不多. 在开发过程中,内部类中使用的最多的还是非静态地成员内部类.不过在特定的情况下,静态内部类也能够发挥其独特的作用. 一.静态内部类的使用目的. 在定义内部类的时候,可以在其前面加上一个权限

  • Java中的ThreadLocal详解

    目录 一.ThreadLocal简介 二.ThreadLocal简单使用 三.ThreadLocal的实现原理 1.set方法源码 2.get方法源码 3.remove方法的实现 四.ThreadLocal不支持继承性 五.InheritableThreadLocal类 六.从ThreadLocalMap看ThreadLocal使用不当的内存泄漏问题 1.基础概念 2.分析ThreadLocalMap内部实现 一.ThreadLocal简介 多线程访问同一个共享变量的时候容易出现并发问题,特别是

  • Java中File类方法详解以及实践

    目录 File类概述 File类常用构造器 File类常用方法 常用方法示例 createTempFile临时文件创建示例 FilenameFilter文件过滤器示例 总结 File类概述 File类是java.io包下代表与平台无关的文件和目录.File可以新建.删除.重命名文件和目录,但是不能访问文件内容本身,如果需要访问内容的话,需要通过输入/输出流进行访问. File类可以使用文件路径字符串创建File实例,路径既可以是绝对路径,也可以是相对路径.一般相对路径的话是由系统属性user.d

  • Java中的 CyclicBarrier详解

    目录 CyclicBarrier简介 CyclicBarrier源码分析 类的继承关系 ​类的属性 类的构造函数 核心函数 - dowait函数 核心函数 - nextGeneration函数 CyclicBarrier简介 对于CountDownLatch,其他线程为游戏玩家,比如英雄联盟,主线程为控制游戏开始的线程.在所有的玩家都准备好之前,主线程是处于等待状态的,也就是游戏不能开始.当所有的玩家准备好之后,下一步的动作实施者为主线程,即开始游戏. 对于CyclicBarrier,假设有一家

  • Java中的静态内部类详解及代码示例

    1. 什么是静态内部类 在Java中有静态代码块.静态变量.静态方法,当然也有静态类,但Java中的静态类只能是Java的内部类,也称为静态嵌套类.静态内部类的定义如下: public class OuterClass { static class StaticInnerClass { ... } } 在介绍静态内部类之前,首先要弄清楚静态内部类与Java其它内部类的区别. 2. 内部类 什么是内部类?将一个类的定义放在另一个类的内部,就是内部类.Java的内部类主要分为成员内部类.局部内部类.

  • Java中初始化块详解及实例代码

    Java中初始化块详解 在Java中,有两种初始化块:静态初始化块和非静态初始化块. 静态初始化块:使用static定义,当类装载到系统时执行一次.若在静态初始化块中想初始化变量,那仅能初始化类变量,即static修饰的数据成员. 非静态初始化块:在每个对象生成时都会被执行一次,可以初始化类的实例变量. 非静态初始化块会在构造函数执行时,且在构造函数主体代码执行之前被运行. 括号里的是初始化块,这里面的代码在创建Java对象时执行,而且在构造器之前执行! 其实初始化块就是构造器的补充,初始化块是

随机推荐