Thread类讲解(二)

简介: Thread类讲解

三、启动线程

启动线程一般用创建的线程对象来调用 start() 方法即可,如下


只有调用 start 才会真正创建线程!!!不调用 start 就是没创建线程(在内核里创键PCB)

注意区分,调用start 和 run 的区别:

       一般情况,我们创建的线程对象调用run方法和start方法时,产生的结果可能会相同,这就会导致我们误以为 run() 和 start() 没有区别!

  直接调用run方法并没有创建线程,只是在原来的线程中运行代码

  而直接调用start方法,则是创建一个新的线程,在新线程中执行代码(和原来的线程是并发的)

我们来看下面代码:

class MyThread extends Thread{
    @Override
    public void run() {
        while(true){
            try {
                System.out.println("hello Thread");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class Demo1 {
    public static void main(String[] args) {
        Thread t1 = new MyThread();
        t1.run();
        //t1.start();
        while(true){
            try {
                System.out.println("hello main");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

当t1调用 start() 方法时的结果:


       我们会发现,t1调用run()方法只会打印“hello Thread”。这是因为是main线程调用的run()方法!!代码顺序执行,同时run()方法是一直循环,所以就不会打印“hello main”。

       而t1调用start()方法,则是创建一个新线程,新线程调用run,所以我们会看到 “hello Thread”和“hello main”都有打印。

四、中断线程

      一般情况下,只有 run方法执行完了,线程才结束,但是有点时候我们想要线程提前结束那该怎么办呢?这就涉及到我们中断线程的方式了。

       中断线程一般会设置标志位,而设置标志位分两种方式:

直接自己定义一个标志位,作为线程是否结束的标记

使用标准库里自带的一个标志位

   (1)自定义标志位

自定义一个变量作为一个标志位,通过对变量的修改来结束run

public class Demo2 {
    private static boolean isQuit = false;
    public static void main(String[] args) {
        Thread t = new Thread(()->{
           while(!isQuit){
               System.out.println("hello Thread");
               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
        });
        t.start();
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        isQuit = true;
        System.out.println("设置让 t 线程结束");
    }
}

(2)使用 Thread.currentThread().isInterrupted() 作为标志位

currentTrhead():是Thread类的静态方法,通过这个方法,可以拿到当前线程的实例

isInterrupted():调用这个方法判断标志位

        在主线程中,通过 t.interrupt 来中断这个线程,设置标志位为 true!

public class Demo3 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
           while(!Thread.currentThread().isInterrupted()){
               System.out.println("hello Thread");
           }
            System.out.println("t 执行完");
        });
        t.start();
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t.interrupt();
        System.out.println("设置让 t 线程结束");
    }
}

注意:interrupt 方法的行为,有两种情况

    ① 当 t 线程在运行状态时,会设置标志位(Thread.currentThread().isInterrupted())为 true.

例如上述代码。

    ② 当 t 线程在阻塞状态时(sleep),就不会设置标志位,而是触发一个 InterruptedException,这个异常会把 sleep 提前唤醒。

上图代码中,处理异常只是打印日志,并没有结束循环。所以这种情况并不能中断线程

这时我们要想中断线程或者其他操作,我们就要在捕获异常的代码块中进程操作!


提示:在 java 中,中断线程并非强制的!!! 由线程自身的代码来进行判断处理的!!!


线程怎么处理,取决于代码怎么写!

五、线程等待

       一般情况下,线程之间的调度顺序,是不确定的!但是可以通过一些特殊操作,来对线程的执行顺序做出干预。

       其中 join 就是一个方法,控制线程之间的结束顺序!!

例如:

      创建一个线程 t,并让他循环打印 “hello” 五次,同时在main线程中调用 t.join()。

public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            for (int i = 0; i < 3; i++) {
                System.out.println("hello");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        System.out.println("main 线程 join 之前");
        t.join();
        System.out.println("main 线程结束");
    }

结果如下:

我们发现 main 线程是等待 t 线程工作结束后才继续执行,结果是确定的。

t.join() 效果就是:让 mian 线程阻塞等待,等到线程之间的结束顺利!!

而如果我们不加 t.join(),运行结果就是随机的了。如下


      在实际开发中很少会用到 无限等待的方法,大多数都是指定了最大等待,避免程序因为死等,导致“卡死”的情况。

join() 带参数的方法如下:

方法 说明
public void join(long millis) 等待线程结束,最多等待 millis 毫秒
public void join(long millis,int nanos)

等待 millis 毫秒 + nanos 纳秒(精确版)


六、获取当前线程引用

方法 说明
public static Thread currentThread() 返回当前线程对象的引用

       获取当前这个线程对应 Thread 对象的引用,哪个线程里面调用,得到的就是哪个线程的引用。

public static void main(String[] args) {
        Thread t = new Thread(()->{
            System.out.println(Thread.currentThread().getName());
        });
        t.start();
        Thread ct = Thread.currentThread();
        System.out.println(ct.getName());
    }

七、线程休眠

       sleep 方法 可以指定休眠时间,让线程休息(相当于阻塞一会)

方法 说明
public static void sleep(long millis) throws InterruptedException 休眠当前线程 millis 毫秒
public static void sleep(long millis,int nanos) throws InterruptedException 休眠 millis 毫秒 + nanos 纳秒
public static void sleep(long millis,int nanos) throws InterruptedException 休眠 millis 毫秒 + nanos 纳秒
    public static void main(String[] args) throws InterruptedException {
        long start = System.currentTimeMillis();//记录开始时间(时间戳)
        Thread.sleep(1000);//让main先休眠 1000毫秒
        Long end = System.currentTimeMillis();//记录结束时间
        System.out.println("耗时:"+(end - start));
    }


 

        sleep 的原理就是让原本在 就绪队列 中的线程,转移到 阻塞队列 中去,等 sleep 的时间到了,再转移到 就绪队列 中。

相关文章
|
4月前
|
算法 Java 调度
深入理解 Thread 类的 Yield 方法
【8月更文挑战第22天】
154 4
|
7月前
|
Java 程序员 调度
Thread类及常见方法
Thread类及常见方法
|
Java 程序员 调度
了解Thread类的其他一些方法及常见属性
了解Thread类的其他一些方法及常见属性
61 0
|
Java 调度
Thread类的方法
Thread类的方法
41 0
Thread 类的基本用法
比较推荐:使用 lambda 表达式创建线程的时候不用重写 run 方法。 不需要显式重写run方法的原因是因为线程的目标方法已经在Lambda表达式中定义了。Lambda表达式是一种用于创建匿名函数的语法糖,它可以将一个方法(或一段代码块)包装为一个函数对象。当您使用Lambda表达式创建线程时,Lambda表达式的内容会被视为线程执行的任务,这个任务会自动成为run方法的实现。
81 0
|
程序员 调度
Thread类的其它方法和属性
Thread类的其它方法和属性
Thread类的其它方法和属性
|
Java 调度
Java多线程的创建与Thread类的方法及使用(上)
Java多线程的创建与Thread类的方法及使用(上)
Java多线程的创建与Thread类的方法及使用(上)