Java多线程——Thread 类及常见方法和线程的基本操作

Posted 爱敲代码的三毛

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java多线程——Thread 类及常见方法和线程的基本操作相关的知识,希望对你有一定的参考价值。


接上一篇 线程的概念和创建
介绍了什么是线程,线程和进程的关系以及线程的创建。再来了解一下Java多线程类 Thread 的常见构造方法和属性

一、Thread 的常见构造方法

方法说明
Thread()创建线程对象
Thread(Runnable target)使用 Runnable对象创建线程
Thread(String name)创建线程对象,并命名
Thread(Runnable target, String name)使用 Runnable 对象创建线程对象,并命名
Thread(ThreadGroup group,Runnable target)线程可以被用来分组管理,分好的组即为线程组

这些构造方法都比较简单,在上一篇博客中也介绍了线程的几种创建方法

public static void main(String[] args) {
        Thread t1 = new Thread("线程1");
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {

            }
        },"线程2");
        
    }

二、Thread常见属性

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

来看一段代码:

当线程没有启动时

public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    System.out.println("线程运行中");
                }
            }
        },"线程");
        System.out.println("id:"+t.getId());
        System.out.println("name:"+t.getName());
        System.out.println("state:"+t.getState());
        System.out.println("priority:"+t.getPriority());
        System.out.println("isDaemon:"+t.isDaemon());
        System.out.println("isAlive:"+t.isAlive());
        System.out.println("isInterrupted:"+t.isInterrupted());

    }

运行结果

当执行 start() 方法启动线程后

public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程运行中");
                }
            }
        },"线程");

        t.start();
        System.out.println("id:"+t.getId());
        System.out.println("name:"+t.getName());
        System.out.println("state:"+t.getState());
        System.out.println("priority:"+t.getPriority());
        System.out.println("isDaemon:"+t.isDaemon());
        System.out.println("isAlive:"+t.isAlive());
        System.out.println("isInterrupted:"+t.isInterrupted());
    }

1.线程名字

Thread 的 name ,存在的意义就是为了方便调试

当我们把线程跑起来的时候,打开jconsole软件就可以看到我们为线程起的名字

2.线程的状态

此处说的线程的状态和这篇文章里 进程 ,的进程的状态是类似的效果。存在的意义都是辅助进行线程调度

3.优先级

优先级,也是和"进程的优先级”是类似的效果,此处的状态和优先级,和内核PCB中的状态优先级并不完全一致。

4.后台线程

关于后台线程,需要记住:JVM会在一个进程的所有非后台线程结束后,才会结束运行,也就是说是否是后台线程,影响了JVM进程是否能够退出。

创建一个新线程,默认不是后台线程。

如果不是后台线程
此时,如果main方法结束了,线程还没有结束,JVM进程不会结束

如果当前线程是后台线程
此时,如果main方法结束,线程还没有结束,JVM进程就会直接结束,同时也就把这个后台线程也一起带走了

5.线程是否存活

判断一个线程是否存活,最简单直观的方法就是 run 方法是否结束了

而用户代码中的 t 这个对象,要靠GC来销毁
Thread t 的生命周期和内核的PCB不一样,它会比 PCB更长

那么就可以通过 isAlive() 方法来判断当前内核中的PCB是否存在

三、线程的基本操作

1.start(启动线程)

前面的博客已经讲过线程的创建和启动了,这里就不细说了。
通过 start()方法来启动一个线程,注意start()和 run()的区别,
调用 start 方法, 才真的在操作系统的底层创建出一个线程。

2.Sleep(休眠线程)

也是我们比较熟悉一组方法,有一点要记得,因为线程的调度是不可控的,所以,这个方法只能保证实际休眠时间是大于等于参数设置的休眠时间的
通过 sleep() 方法来休眠一个线程,sleep() 是一个类方法

public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //休眠1秒
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }

Sleep 这个方法,本质上就是把线程PCB给从就绪队列,移动到了阻塞队列,只有当 Sleep时间到了或者抛出异常了才会回到就绪队列中

3.currentThread(获取当前线程引用)

currentThread 能够获取到当前线程对应的 Thread 实例的引用,相当于 this关键字

但是需要注意的是,如果是使用 Runnable 或者 lambda 的方式来创建的线程,就无法使用 this 了。
this指向的是 Runnable 实例,而不是Thread 实例了,此时也就没有 getId 方法了。

4.interrupt(中断线程)

让线程结束的关键,就是让线程对应的入口方法,执行完毕
常见的创建线程的方式有
继承 Thread 重写 run
实现 Runnable 重写 run
lambda

像这种情况,线程一下就执行完了


更多情况下,线程不一定这么快就能执行完 run 方法 ,如果 run 方法里面带的是一个死循环,此时这个线程就会一直持续运行,直到整个进程结束


实际开发中,肯定是不希望线程的 run 就是一个 死循环,更希望控制这个线程,按照咱们的需要随时结束
为了实现这个效果,就有一些常见的方法

简单粗暴的办法,直接定义一个 flag 变量,这种方式并不怎么好。

还有一种办法就是使用标准库里的内置的标记

获取线程内置的标记位:线程的 isInterrupted() 判断当前线程是不是应该结束循环,且默认返回是 true

修改线程内置的标记位:Thread.interrupted() 来修改这个标记位

来看一段代码:


4秒之后, interrupt 方法好像并没有修改这个标记位,循环看起来还是在继续执行,同时这里有个异常。


这里的 interrupt 方法有两种行为

1.如果当前线程正在运行中,此时就会修改 Thread.islnterruppted() 标记位为 true

2.如果当前线程 正在 sleep、wait、等待锁,此时就会触发 InterruptedException

只要在 catch 里面加一个 break 就好了


isInterrupted() 这个是 Thread 的实例方法,
还有和这个方法类似的:
interrupted() 这个是Thread的类方法(static)

那么这两者有什么区别呢?
列如:调用 interrupt() 方法,把标记位设为 true,就应该结束循环

当调用 静态的 interrupted 来判定标记位的时候,就会返回 true,同时就会把标记位再改回 false,下次再调用interrupted() 就返回 false

如果是调用非静态的 isInterrupted() 来判断标记位,也会返回 true,但不会对标记位进行修改,后面再调用isInterrupted() 的时候仍然返回 true

5.join(线程等待)

线程和线程之间,调度顺序是完全不确定的(取决于操作系统调度器自身的实现)
但是有的时候希望这里的顺序是可控的
此时线程等待,就是一种办法,这里的线程等待,主要就是控制线程结束的先后顺序

通过 t.join 来阻塞线程,此时线程处于阻塞等待,代码就不继续往下走了,具体来说就是,操作系统短时间内不会把这个线程调度到 CPU上了。

执行到 start 方法的时候,就会立刻创建出一个新的线程来
同时 main 这个线程也立刻往下执行,就执行到了 t,join
执行到 t.join 的时候就会发现,当前 t 线程还是再运行中
只要 t 在运行中,join 方法就会一直阻塞等待,一直等到 t 线程执行结束(run执行完),才会执行后面的代码

直接使用 join()就是相当于死等,在开发中死等是比较危险的。
join 还有个有参数版本,给了个参数就不是死等了

四、线程的状态

线程的状态,用于辅助系统对于线程进行调度这样的属性

线程的状态是一个枚举类型 Thread.State


运行结果

NEW:Thread 对象创建出来了,但是内核的PCB还没创建出来

RUNNABLE:当前的PCB已经创建出来了,同时这个PCB随时待命(就绪状态),这个线程可能是正在CPU上运行,也可能是在就绪队列中排队

TIMED_WAITING:表示当前的PCB在阻塞队列中等待,这样的等待是一个带有结束时间的等待,sleep就会触发这个状态

WAITING:线程中如果调用了 wait 方法,也会阻塞等待。此时处于 WAITING状态(死等),除非是其他线程唤醒了该线程

BLOCKED:线程中尝试进行加锁,结果发现锁已经被其它线程占用了。此时线程也会阻塞等待。这个等待就会在其它线程释放锁之后,被唤醒

TERMINATED:表示当前PCB已经结束了,Thread 对象还在,此时调用获取状态,得到的就是这个状态

yield()
yield()方法的效果是让线程主动让出CPU,但是不改变线程的状态。
不过这个方法一般不会使用

不用 yield 方法的时候,t1和t2线程打印的次数基本是五五开,
如果使用 yield 方法,t2打印明细比 t1 多。


下一篇线程的安全,马上更新!

以上是关于Java多线程——Thread 类及常见方法和线程的基本操作的主要内容,如果未能解决你的问题,请参考以下文章

Java中Thread类及常见方法

Java中Thread类及常见方法

Java中Thread类及常见方法

多线程的Thread 类及方法

Thread 类及常见方法

Java遨游在多线程的知识体系中