总结Thred的基本用法,包括线程创建,线程中断,线程等待,线程休眠,获取线程实例

1.创建线程的几种常见的写法

1.创建一个类继承Thread,重写run(这个写法,线程和任务内容是绑定在一起的)

class MyThread extends Thread{
    
    
    @Override
    public  void run(){
    
    
        while(true){
    
    
            System.out.println("hello Thread");
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}
public class Demo1 {
    
    
    public static void main(String[] args) {
    
    
        // 创建一个线程
        // Java 中创建线程, 离不开一个关键的类, Thread
        // 一种比较朴素的创建线程的方式, 是写一个子类, 继承 Thread, 重写其中的 run 方法.
        Thread t=new MyThread();
        t.start();
        while(true){
    
    
            System.out.println("hello main");
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }

    }
}

2.创建一个类,实现Runnable接口,重写run.

此处创建的Runnable,相当于定义了一个任务(代码要干嘛),还是需要Thread实例把任务交给Thread,还是调用start来创建具体的线程

这个写法,线程和任务是分离开的(更好的解耦合)

模块/代码之间,关联关系越紧密,就认为耦合性越高,关联关系越不紧密,认为耦合性越低(写代码是要追求低耦合,高内聚)

class MyRunnable implements Runnable{
    
    

    @Override
    public void run() {
    
    
        while(true){
    
    
            System.out.println("hello thread");
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}
public class Demo2 {
    
    
    public static void main(String[] args) {
    
    
        MyRunnable runnable=new MyRunnable();
        Thread t=new Thread(runnable);
        t.start();
        while(true){
    
    
            System.out.println("hello main");
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1VidjJeR-1661760136664)(…/md文件图片备份/image-20220729155530456.png)]

问:为什么Thread,Runnable,InterruptedException都不需要import?(啥样的类,不需要import就能直接使用)

答:要么类在同一个包里,要么这个类是在java.lang里(lang是被默认导入了)

3.仍然是使用继承Thread类,但是不再显式继承,而是使用“匿名内部类”

Thread实例是Java中对于线程的表示,实际上要真的跑起来,还是需要操作系统里面的线程

public class Demo3 {
    
    
    public static void main(String[] args) {
    
    
        Thread t=new Thread(){
    
    
            @Override
            public void run() {
    
    
                while(true){
    
    
                    System.out.println("hello thread");
                    try {
    
    
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
            }
        };
        t.start();
    }
}

4.使用Runnable,是匿名内部类的方式使用

public class Demo4 {
    
    
    public static void main(String[] args) {
    
    
        Thread t=new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                while(true){
    
    
                    System.out.println("hello thread");
                    try {
    
    
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
            }
        });
        t.start();
    }
}

5.使用lambda表达式,来定义任务(推荐做法)

public class Demo5 {
    
    
    public static void main(String[] args) {
    
    
        Thread t=new Thread(()->{
    
    
            while(true){
    
    
                System.out.println("hello thread");
                try {
    
    
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
        });
        t.start();
    }
}

lambda表达式本质上就仅仅是一个匿名函数(没有名字的,只使用一次的函数)

实际上,线程创建还有其他的创建方式,基于Callable/FutureTask的方式创建,基于线程池的方式创建…

一个经典的面试题:Java中创建线程有哪些方式?

答:至少7种

2.线程中断

isInterrupted()

启动一个线程start()

  • 创建Thread实例,并没用真的在操作系统内核里创建出线程
  • 调用start,才是真正在系统里创建出线程,才真正开始执行任务

线程的执行结束:只要让线程的入口方法执行完了,线程就随着结束了(主线程的入口方法,就可以视为main方法)

对应的,所谓的“中断线程”就是让线程尽快把入口方法执行结束

法一:直接使用自己的标志位来区分线程是否要结束

public class Demo9 {
    
    
    // 用一个布尔变量表示线程是否要结束.
    // 这个变量是一个成员变量, 而不是局部变量
    private static boolean isQuit = false;
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread t=new Thread(()->{
    
    
            System.out.println("新线程运行中");
            while(!isQuit){
    
    
                try {
    
    
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
            System.out.println("新线程执行结束");
        });
        t.start();
        Thread.sleep(5000);
        System.out.println("控制线程退出");
        isQuit=true;
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fsvExMrh-1661760263272)(…/md文件图片备份/image-20220730222130022.png)]

法二:使用Thread自带的标志位

public class Demo10 {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        Thread t=new Thread(()->{
    
    
            while(!Thread.currentThread().isInterrupted()){
    
    
                System.out.println("线程运行中");
                try {
    
    
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
    
    
//                    e.printStackTrace();
                    // [1] 立即退出
                    break;

                   // System.out.println("新线程即将退出!");
                    // [2] 稍后退出, 此处的 sleep 可以换成任意的用来收尾工作的代码
//                    try {
    
    
//                        Thread.sleep(3000);
//                    } catch (InterruptedException ex) {
    
    
//                        ex.printStackTrace();
//                    }
//                    break;

                    // [3] 不退出! 啥都不做, 就相当于忽略了异常.
                }
            }
            System.out.println("新线程已经退出");
        });
        t.start();
        Thread.sleep(5000);
        System.out.println("控制新线程退出!");
        t.interrupt();
    }
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4ynhJa1r-1661760263273)(…/md文件图片备份/image-20220730223921322.png)]

注意理解interrupt方法的行为:

  • 如果t线程没有处在阻塞状态,此时interrupt就会修改内置的标志位
  • 如果t线程正再处于阻塞状态,此时interrupt就让线程内部产生阻塞的方法,例如sleep抛出异常InterruptedException

这里解释一下这两句话的意思,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eBaKRZ14-1661760263274)(…/md文件图片备份/image-20220731183518086.png)]

这里的线程里由于有sleep方法,让线程内部产生阻塞方法也就是sleep抛出异常,然后被catch捕捉到,执行catch里面的代码,正是这样的捕获操作,程序员就可以自行控制线程的退出行为了

  • 如果你想立即退出,在catch里加个break,就退出了;
  • 如果你想等一会再退出,你可以在catch写代码,最后写break,这样执行完后也能退出
  • 如果你不想退出,就不加break,这样执行完catch后,还是会回到while进行判断,但是由于interrupt并没有修改内置标志位,所以循环(线程)不会退出

但是如果线程里面没有令线程产生阻塞的方法,比如sleep方法这种,这时interrupt就会直接修改内置标志位,当你代码执行完回到while进行判断时,由于内置位已经修改,所以退出循环(线程)

法三:使用 thread 对象的 interrupted() 方法通知线程结束

public class ThreadDemo {
    
    
    private static class MyRunnable implements Runnable {
    
    
        @Override

        public void run () {
    
    
            // 两种方法均可以

            while ( ! Thread . interrupted ()) {
    
    
            //while (!Thread.currentThread().isInterrupted()) {
    
    

                System . out . println (Thread . currentThread (). getName ()
                        + ": 别管我,我忙着转账呢 !");
                try {
    
    
                    Thread . sleep (1000);
               } catch (InterruptedException e) {
    
    
                    e . printStackTrace ();
                    System . out . println (Thread . currentThread (). getName ()
                            + ": 有内鬼,终止交易! ");
                    // 注意此处的 break

                    break ;
               }
           }
            System . out . println (Thread . currentThread (). getName ()
                    + ": 啊!险些误了大事");
       }
   }
    public static void main (String [] args) throws InterruptedException {
    
    
        MyRunnable target = new MyRunnable ();
        Thread thread = new Thread (target , "李四 ");
        System . out . println (Thread . currentThread (). getName ()
                + ": 让李四开始转账。 ");
        thread . start ();
        Thread . sleep (10 * 1000);
        System . out . println (Thread . currentThread (). getName ()
                + ": 老板来电话了,得赶紧通知李四对方是个骗子! ");
        thread . interrupt ();
   }
}

thread 收到通知的方式有两种:

  • 如果线程因为调用 wait/join/sleep 等方法而阻塞挂起,则以 InterruptedException 异常的形式通 知,清除中断标志,当出现 InterruptedException 的时候, 要不要结束线程取决于 catch 中代码的写法. 可以选择 忽略这个异常, 也可以跳出循环结束线程.
  • 否则,只是内部的一个中断标志被设置, thread 可以通过,Thread.interrupted() 判断当前线程的中断标志被设置,清除中断标志,Thread.currentThread().isInterrupted() 判断指定线程的中断标志被设置,不清除中断标志

interrupted跟isInterrupted的区别是:interrupted标志会自动清除,也就是说内置标志位本来是false,外面调用了interrupt,就会从false变成true,但读取到while(!Thread.interrupted()),会读到这个true,但是读完之后,这个标志位就自动恢复成false了


Java这里的线程终止,设定的是比较绕的,把决定权给要被结束的线程自身

操作系统原生的线程库,中断的时候,决定权是调用者

3.线程等待join

线程之间的执行顺序是完全随机的,看系统的调度

我们不能确定两个线程的开始执行顺序,但是可以通过join来控制两个线程的结束

public class Demo11 {
    
    
    private static Thread t1 = null;
    private static Thread t2 = null;
    public static void main(String[] args) throws InterruptedException {
    
    
        System.out.println("main begin");
        t1=new Thread(()->{
    
    
            System.out.println("t1 begin");
            try {
    
    
                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println("t1 end");
        });
        t1.start();
        t1.join();//让t1先执行完
        t2=new Thread(()->{
    
    
            System.out.println("t2 begin");
            try {
    
    

                Thread.sleep(1000);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
            System.out.println("t2 end");
        });
        t2.start();
        t2.join();//让t2执行完
        System.out.println("main end");
    }
}

运行结果:main begin
		t1 begin
		t1 end
		t2 begin
		t2 end
		main end


这个代码,主线程先执行t1,然后join等待t1结束,t1结束之后,主线程在启动t2,再等待t2结束

join的行为:

  • 如果被等待的线程还没执行完,就阻塞等待
  • 如果被等待的线程已经执行完了,就直接返回

比如上面的代码,开启线程t1后,t1调用join,t1就是被等待线程,其他线程要等待t1执行完,如果开启t2,t2调用join,main等待t2结束

方法 说明
public void join() 等待线程结束(死等)
public void join(long mills) 等待线程结束,最多等mills毫秒
public void join(long mills.int nanos) 同理但可以更高精度

第三个是join(100,1000)相当于等待100.001ms

4.线程休眠sleep

在这里插入图片描述

操作系统内核中管理PCB的链表实际上是有多个的

问:写了个sleep(1000)就一定会在1000ms之后就上CPU运行吗

答:不一定,1000ms之后只是把这个PCB放回了就绪队列,至于说这个线程啥时候能上CPU执行,还的看调度器的心情

方法 说明
public static void sleep(long millis) throws InterruptedException 休眠当前线程millis毫秒
public static void sleep(long millis,int nanos) throws InterruptedException 可以更高精度的休眠

5.获取当前线程引用

方法 说明
public static Thread currentThread(); 返回当前线程对象的引用
Thread.currentThread()//哪个线程调用,就返回哪个线程的对象

猜你喜欢

转载自blog.csdn.net/HBINen/article/details/126586808