java线程之Thread类的基本用法

Posted 小鱼不会骑车

tags:

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

Thread类的基本用法

小鱼在上一篇博客详细的讲解了如何创建线程,java使用Thread类来创建多线程,但是对于好多没有相关经验的人来说,比较不容易理解的地方在于操作系统调度的执行过程. 我们通过下面代码举例:

    public static void main(String[] args) 
        Thread t = new Thread(() -> 
            System.out.println("hello t");
        );
        //调用start(),创建一个新的线程
        t.start();
 
        System.out.println("hello main");
    

我们的java在启动时,会创建一个主线程,此时主线程的名字就是main,接下来我们执行main线程里面的一条条代码了,当我们执行到t.start()时,系统就会自动帮我们创建一个新的线程,并且会自动帮我们调用该线程中的run()方法,此时的run()方法就可以认为是个入口,一些重要的实现逻辑将都在里面实现.

我们通过上述代码举例:

当我们实例一个thread并且调用start()方法之后, main 线程和 t 线程就是(并发+并行)的过程,由于多线程有一个万恶之源–抢占机制,所以就会导致我们无法预测到程序是优先执行"hello t" 还是 “hello main”,并且对于谁先结束线程的任务,我们也是不确定的,可能是main线程优先结束,也可能是t线程优先结束.

创建线程是看start的顺序,但是具体的线程对应的任务什么时候执行,要看系统调度器。

1. Thread类的构造方法

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

关于构造方法的讲解在这篇文章中详细的讲到了,包括如何创建一个线程,以及创建线程需要注意的事项都在这里一一列举了出来多线程的创建及其注意事项

2. Thread的几个常见属性

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

常见属性

(1) getid() : ID表示线程的身份.就类似于我们的身份证一样,是独一无二的.这里的方法获取到的是线程在JVM中的身份标识,线程的标识有好多个,在内核的PCb上有标识,在用户态线程数据库中也有标识(操作系统的线程库)

(2) getName() : 获取当前线程的名字,多用于调试时使用.

(3) getState() :获取当前线程的状态,状态表示线程当前所属的一个情况.

(4) getPriority() : 优先级高的线程理论上来说更容易被调度到,但实际上并没有什么区别.大概可以理解为,你建议我不要晚睡,但也仅仅是建议,我可以不听.

(5) isDaemon() :daemon称为"守护线程",也可以称为后台线程,如果是后台线程就会返回true,前台线程则是false,我们可以这么理解后台线程,当我们手机打开qq这个app时,此时qq就是前台运行,但是由于我们后续可能会切换到快手app,此时qq就来到后台运行了.

一个线程创建出现默认是前台线程,前台线程会阻止进程的退出,进程只有在所有的前台线程都执行完之后才可以退出,但是对于后台进程,并不会阻止进程的结束.

JVM会在一个进程的所有非后台进程结束后,才会结束运行。

(6)isAlive() : 是否存活,即run()方法是否结束。

public class Demo4 
    public static void main(String[] args) 
        Thread t = new Thread(new Runnable() 
            @Override
            public void run() 
                System.out.println("t 线程正在运行");
            
        );
        System.out.println("t线程是否存活 "+t.isAlive());
        t.start();
        System.out.println("t线程是否存活 "+t.isAlive());

        try 
            Thread.sleep(1000);
            System.out.println("t线程是否存活 "+t.isAlive());
         catch (InterruptedException e) 
            e.printStackTrace();
        

    

如果 t 的run还没跑,isAlive就为false;
如果 t 的run正在跑,isAlive就为true;
如果 t 的run跑完了,isAlive就为false;

执行结果:

此时我们将上述涉及到的属性的状态打印出来:

使用setDaemon(布尔值)方法设置一个线程为后台,true表示后台线程。

class MyThread1 extends Thread
    @Override
    public void run() 
        System.out.println("hello  t");
    

public class Demo1 
    public static void main(String[] args) 
        Thread t = new MyThread1();
        t.start();

        System.out.println("hello main");
        try 
            Thread.sleep(1000);
         catch (InterruptedException e) 
            e.printStackTrace();
        
        //设置为后台线程
        t.setDaemon(true);
        System.out.println("是否是后台线程 "+ t.isDaemon());
    

执行结果:


上述操作都是获取到的一瞬间的状态,不是持续的状态。

start()和run()方法的区别:

当我们调用start()这个方法时,创建一个新的线程(有一个向系统创建一个线程的这么一个动作),此时这个线程会由系统自动调用run()方法(入口方法).并且这个run()方法是在这个新建的线程中运行的.
但是如果是程序员手动调用run()方法的话,并不会新建一个线程,并且该方法是在调用这个方法的线程中执行的.

获取当前线程的引用:

方法说明
public static Thread currentThread();返回当前线程对象的引用
 public static void main(String[] args) 
 		//获取当前线程的引用
        Thread t = Thread.currentThread();
        //通过当前线程的引用调用当前线程的名字
        System.out.println("当前线程名字 : "+t.getName());
    

执行结果:

休眠当前线程:

方法说明
public static void sleep(long millis) throws InterruptedException休眠当前线程 millis毫秒
public static void sleep(long millis, int nanos) throwsInterruptedException可以更高精度的休眠
   public static void main(String[] args) 
        Thread t = new Thread(()->
            System.out.println("hello t");
        );
        t.start();
        try 
            //睡眠1000ms,也可以称为阻塞1000ms
             Thread.sleep(1000);
         catch (InterruptedException e) 
            e.printStackTrace();
        
        System.out.println("hello main");
    

睡眠的状态就相当于这个线程被阻塞了,阻塞的时间由里面的参数决定,在这个线程阻塞的时间并不影响其他线程执行,可以理解为~我们在打游戏,打着打着未成年时间限制弹了出来,这时候就要求我们停止打游戏这个行为,只有等这个等待时间过去之后才可以继续打游戏.

:等待的过程中什么都不能做,相当于被定身了一样!!!

线程中断

中断线程,就是让线程尽快的把入口方法(run方法)执行结束。有2种方法。

(1) 直接手动创建标志位来区分线程是否要结束.

public class ThreadDemo5 
    static boolean flog = true;
    public static void main(String[] args)  
        Thread t = new Thread(()->
           while (flog)
               try 
                   Thread.sleep(500);
                catch (InterruptedException e) 
                   e.printStackTrace();
               
               System.out.println("线程运行中~~");
           
            System.out.println("t 线程结束");
        );
        t.start();
        try 
            //睡眠3000ms
            Thread.sleep(3000);
            //将标志位设置为false终止上面的while()
            System.out.println("控制新线程结束");
            flog = false;
         catch (InterruptedException e) 
            e.printStackTrace();
        
    


运行结果:


有些线程可能会存在一些循环需要执行的情况,但是可能只是需要执行一定时间,并不是一直执行,所以就可以通过sleep()来控制这个线程结束的时间.

需要注意的是:flog需要是成员变量,局部变量是线程私有的,其余线程不能进行修改,但是成员变量是处于进程的数据区,是共享的资源,所以可以访问和修改~~

Thread内置了标志位,不需要手动创建标志位。

(2)Thread内置的标志位

public class ThreadDemo5 
    static boolean flog = true;
    public static void main(String[] args)  
        Thread t = new Thread(()->
           while (!Thread.currentThread().isInterrupted())
               try 
                   Thread.sleep(500);
                catch (InterruptedException e) 
                   e.printStackTrace();
               
               System.out.println("线程运行中~~");
           
            System.out.println("t 线程结束");
        );
        t.start();
        try 
            //睡眠3000ms
            Thread.sleep(3000);
            //将标志位设置为false终止上面的while()
            System.out.println("控制新线程结束");
           t.interrupt();
         catch (InterruptedException e) 
            e.printStackTrace();
        
    


Thread.currentThread().这个是Thread类的静态方法,通过这个方法可以获取当前线程,类似this;

isInterrupted()方法是一个判定内置的标志位,默认是false,true表示线程要中断。现在运行,查看结果。

为什么在抛出异常后线程还是在不停的执行呢?

那是因为当代码执行到interrupt()时会做两件事:

(1) 如果t 线程没有处于阻塞状态时,这时候interrupt()就会修改内置标志位,将isInterrupted的状态设置为true;

(2) 如果 t 线程处于阻塞状态时,此时interrupt()会通过触发异常,将阻塞状态(sleep)提前唤醒,但是由于把sleep唤醒会导致标志位变成false,也就是将标志位清空,如果标志位本身就是false则不变.由于我们的循环条件是while(!Thread.currentThread().isInterrupted()),又因为我们的标志位又变成了false,所以我们可以继续执行这个循环.

实际中能产生阻塞的方法有很多,并不是只有sleep,这里只是用sleep举例子.

由于计算机的执行速度时很快的,所以阻塞状态大概占线程执行状态的99.99%,所以会有更大的几率触发(2).

当然,即使是大部分时间都在阻塞状态,但是该线程始终是在阻塞状态和正常运行的状态之间切换.

当然,我们可以通过break关键字,当抛出异常时直接break就可以不再执行循环,之后就可以结束线程,这就相当于我正在打游戏,我妈喊我吃饭,此时我不敢抗拒,只能去吃饭.

代码如下:

public class ThreadDemo5 
    public static void main(String[] args)  
        Thread t = new Thread(()->
           while (!Thread.currentThread().isInterrupted())
               try 
                   Thread.sleep(500);
                catch (InterruptedException e) 
                   e.printStackTrace();
                    break;
               
               System.out.println("线程运行中~~");
           
            System.out.println("t 线程结束");
        );
        t.start();
        try 
            //睡眠3000ms
            Thread.sleep(3000);
            //将标志位设置为false终止上面的while()
            System.out.println("控制新线程结束");
           t.interrupt();
         catch (InterruptedException e) 
            e.printStackTrace();
        
    


此时的运行结果:


当然,由于我们捕捉到了异常,我们不仅可以直接让他break,还可以让他先等待一会再结束执行.

就像我们在打电话一样:

比如我正在打游戏,女朋友让我出去买菜,此时我不搭理她,这个时候就相当于,你说你的我干我的.

但是呢?过了一会女朋友又来跟我说让我买菜去,我因为怕她生气嘛,所以我跟她说,五分钟,五分钟就好…等五分钟一到,我就退出游戏买菜去了.

当我买完菜的时候,刚躺到床上打开游戏,此时厨房传来女朋友的尖叫声,那我必须立刻放下手机去看看发生了什么事情…

总结三种状态;(1)立即退出 (2) 等待一会再退出 (3) 不退出,你说你的我做我的.

由我们自己来决定什么时候退出.

如果是直接写死的话,让我收到命令就必须退出,假如我在跟老板汇报工作,此时女朋友让我去买菜,如果我把老板电话挂掉,那自己也就寄了.

只有自己才有资格决定自己要做什么事情,别人的话之能当作意见,只有自己是为自己好.

等待一个线程

线程是一个随机调度过程,等待线程,就是控制两个线程的结束顺序.

但是呢?我们可以通过过join()方法来控制线程的执行顺序.

大家看下面的代码以及执行结果:


由于线程是抢占式执行的,所以可能这次是先打印 main 但是下次可能就先打印t了,可是程序员不喜欢这种不确定性,所以我们可以通过join()方法来控制顺序.

大家看下面的代码和执行结果:


当我们在 main 线程中调用 t.join() ,此时只有当 t 线程执行完之后,才会去执行 main 线程中的内容,我们也可以理解为,在哪个线程中调用join()方法,那么就哪个线程阻塞,只有当调用这个方法的引用所对应的线程执行完之后,才可以继续执行后续代码~


当然,这个join()也是可以传参数的,假如 t 线程一直没有执行完,那么我这个线程就不工作了? 就像我约女神见面一样,我等了一天,两天,三天都没等到,难道我还要等一辈子? 所以此时我们就可以传一个参数,作为最大等待时间,超过这个时间我就不等了,世界上美女那么多,我换个人舔还不行?

join方法自身也会阻塞,Java线程但凡是阻塞的方法都可能抛出这个异常(throws InterruptedException)。

join有两种行为:

(1)如果被等待的线程还没有执行结束,那么直接阻塞等待;

例如:我接我孩子放学,他5.00放学,我4.50到了,此时我就需要等待他放学,菜才能回家.

(2)如果被等待的线程已经执行结束了,就直接返回。

例如:我接我孩子放学,他5.00放学,我5.10才到,那么我就不用等,可以直接接回家.

join方法不带参数的时候,就是死等,一直等待下去。方法带参数的时候,如果超过了等待的最大时间,就开始执行下一步的代码。一般写带参数的join方法。

JavaEE--Thread 类的基本用法(不看你会后悔的嘿嘿)

Thread类是JVM用来管理线程的一个类,换句话说,每个线程都唯一对应着一个Thread对象.

因此,认识和掌握Thread类弥足重要.

本文将从

  1. 线程创建
  2. 线程中断
  3. 线程等待
  4. 线程休眠
  5. 获取线程实例

等方面来进行具体说明.

1)线程创建

方法1:通过创建Thread类的子类并重写run () 方法

class MyThread extends Thread
    @Override
    public void run() 
        System.out.println("thread");
    

public class Thread1 
    public static void main(String[] args) 
        Thread thread = new MyThread();
        thread.start();
        System.out.println("main");
    

方法2:通过创建Runnable接口的实现类并重写run ()方法,传实现类的对象作为构造器

class MyRunnable implements Runnable
    @Override
    public void run() 
        System.out.println("thread");
    

public class Thread2 
    public static void main(String[] args) 
        Thread thread = new Thread(new MyThread());
        thread.start();
        System.out.println("main");
    

方法3:使用匿名类,new类,也就是创建该类的子类

public class Thread3 
    public static void main(String[] args) 
        Thread thread = new Thread()
            @Override
            public void run() 
                System.out.println("thread");
            
        ;
        thread.start();
        System.out.println("main");
    

方法4:使用匿名内部类,new接口,也就是创建该接口的实现类

public class Thread4 
    public static void main(String[] args) 
        Thread thread = new Thread(new Runnable() 
            @Override
            public void run() 
                System.out.println("thread");
            
        );
        thread.start();
        System.out.println("main");
    

方法5:使用lambda表达式(日常开发中使用最多的形式)

public class Thread5 
    public static void main(String[] args) 
        Thread thread = new Thread(()->
            System.out.println("thread");
        );
        thread.start();
        System.out.println("main");

    

2)线程中断

顾名思义,也就是让线程停止.

本质上而言,让线程停止,方法就一种 -->执行完线程入口方法(run ()方法).

只不过此处可能是正常执行结束,也可能是因为异常而导致结束.

目前常见的有以下两种方式:
1.使用自定义的变量来作为标志位
2.使用Thread提供的变量来作为标志位

下面来介绍:

方法1.使用自定义的变量来作为标志位

public class ThreadDemo9 
    public static boolean isQuit = false;

    public static void main(String[] args) 
        // boolean isQuit = false;

        Thread t = new Thread(() -> 
            while (!isQuit) 
                System.out.println("hello t");
                try 
                    Thread.sleep(1000);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
            
            System.out.println("t 线程终止");
        );

        t.start();

        // 在主线程中, 修改 isQuit
        try 
            Thread.sleep(3000);
         catch (InterruptedException e) 
            e.printStackTrace();
        
        isQuit = true;
    

执行结果:

此处代码的逻辑大概就是,主线程sleep ()3秒后,t线程里的循环差不多执行3次(sleep()了3秒),此时isQUit被置为ture,t线程里循环结束.t线程继续向下执行逻辑,最终t线程正常结束.

假设:此处设置的isQuti不是类变量,而是局部变量,可行吗?

结果是显然不行的.那到底是什么原因呢?其实是因为-->lambda表达式存在变量捕获.

变量捕获,只能捕获到由1)final修饰2)实际上final的局部变量.

但是如果该局部变量的值被修改了,那么lamoda表达式就捕获不到该值了.

所以此处该如何做呢? --> 设置成类变量.也就是由static修饰的变量.

因为类变量不受变量捕获的限制.

方法2:使用Thread提供的变量来作为标志位

使用Thread.currentThreadOisInterrupted0 代替自定义标志位.

Thread 内部包含了一个 boolean 类型的变量作为线程是否被中断的标记.

public class ThreadDemo10 
    public static void main(String[] args) 
        Thread t = new Thread(() -> 
            // currentThread 是获取到当前线程实例.
            // 此处 currentThread 得到的对象就是 t
            // isInterrupted 就是 t 对象里自带的一个标志位.
            while (!Thread.currentThread().isInterrupted()) 
                System.out.println("hello t");
                try 
                    Thread.sleep(1000);
                 catch (InterruptedException e) 
                    e.printStackTrace();
                
            
        );

        t.start();

        try 
            Thread.sleep(3000);
         catch (InterruptedException e) 
            e.printStackTrace();
        
        // 把 t 内部的标志位给设置成 true
        t.interrupt();
    

 

原因如下:

首先需要明白interrupt 方法的作用:

1)设置标志位为 true

2)如果该线程正在阻塞中(比如在执行 sleep)此时就会把阻塞状态唤醒.通过抛出异常的方式让 sleep 立即结束.(线程在执行sleep(),join (),wait() 方法时,都会进入阻塞状态)

同时需要注意:
当 sleep 被唤醒的时候, sleep 会自动的把 Thread线程内置的标志位给清空(true -> false).这就导致下次循环,循环仍然可以继续执行了!!!

因此:如果需要结束循环,就得在 catch 中搞个 break.

此处再具体说明一下sleep() 的工作原理:(分3种情况)
1)如果 sleep 执行的时候看到这个标志位是 false --> sleep 正常进行休眠操作.

2)如果当前标志位为 true,sleep 无论是刚刚执行还是已经执行了一半,都会触发两件事 :1.立即抛异常2.清空标志位为 false.
3)如果设置 interrupt 的时候,恰好, sleep 刚醒~~ 这个时候赶巧了,执行到下一轮循环的条件,就直接结束了但是这种概率非常低,毕竟 sleep 的时间已经占据了整个循环体的 99.99999999% 的时间了.几乎可以视为不可能事件.

那可能就又会有人问了,为啥 sleep 要清空标志位呢???
目的就是为了让线程自身能够对于线程何时结束,有一个更明确的控制~
当前interrupt 方法,不是让线程立即结束,而是告诉他,你该结束了,至于他是否真的要结束,立即结束还是等会结束,都是代码来灵活控制的。
interrupt 只是通知,而不是"命令".
为什么这么说呢?原因就是,t线程是否结束,是取决于自身的,也就是说,根据catch里的逻辑实现,可以结束,也可以不结束.

 那肯定又会有人问了:那为啥java 这里 不强制设定成命令结束"的操作? 只要调用 interrupt 就立即结束?? 

主要是设定成这种,非常不友好的~

线程 t 何时结束,一定是 t 自己最清楚~~ 交给 t 自身来决定比较好~~

映射到生活:如果正在跟领导打电话,你的女票让你去帮她拿东西.那此时,肯定是工作优先嘛!!些竟拿东西不是太重要的事,可以先搁置一会!!

3)线程等待

线程之间是并发执行的,操作系统对于线程的调度是无序的.无法判定两个线程谁先执行结束, 谁后执行结束!!!

在某些业务场景下,需要明确规定线程的结束顺序.可以使用线程等待来实现 -->join 方法

public class ThreadDemo 
    public static void main(String[] args) throws InterruptedException 
        Runnable target = () -> 
            for (int i = 0; i < 10; i++) 
                try 
                    System.out.println(Thread.currentThread().getName() 
                                       + ": 我还在工作!");
                    Thread.sleep(1000);
                catch (InterruptedException e) 
                    e.printStackTrace();
               
           
            System.out.println(Thread.currentThread().getName() + ": 我结束了!");
       ;
        Thread thread1 = new Thread(target, "李四");
        Thread thread2 = new Thread(target, "王五");
        System.out.println("先让李四开始工作");
        thread1.start();
        thread1.join();
        System.out.println("李四工作结束了,让王五开始工作");
        thread2.start();
        thread2.join();
        System.out.println("王五工作结束了");
   

输出结果:

当前是在main线程里执行的thread1.join()(也就是说是让main线程等待thread1线程).

那么在main线程里执行的thread1.join0 的时候,此时有两种情况:

1)如果 thread线程仍在继续运行,那么main线程就会暂时不参与程调度,等待thread1 线程结束main线程才继续执行代码逻辑.
2)如果thread1线程已经结束,那么main线程也就不用等待了,就直接继续执行自己的代码逻辑了.

但不管是哪种情况,都能保证thread1线程是先于main线程结束的.

4)线程休眠

方法:public static void sleep(long millis) throws InterruptedException

说明:

1)单位是ms

2)sleep是Thread类里的静态方法,通过类名可以直接调用.

3)作用是休眠当前进程.

4)需要用try-catch处理InterruptedException中断异常(意思就是 sleep 睡眠过程中,还没到点就提前唤醒了).

5)获取线程实例

方法:public static Thread currentThread()

说明:在哪个线程里调用,返回的就是哪个线程的对象引用.

public class ThreadDemo 
    public static void main(String[] args) 
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName());
   

输出结果:main

多提一句:多线程编程在Java中很常见,也很重要.务必掌握!!!

今天不想敲代码,所以才去敲.

uu们加油呀!!

 

以上是关于java线程之Thread类的基本用法的主要内容,如果未能解决你的问题,请参考以下文章

Java多线程之线程的创建与Thread类的使用

java高级用法之:绑定CPU的线程Thread-Affinity

JavaEE--Thread 类的基本用法(不看你会后悔的嘿嘿)

Java线程之 InterruptedException 异常

Java并发编程-Thread类的使用

一脚踩进java之基础篇43——线程Thread和Runnable