Thread的基本用法

1.创建线程

1.1 继承Thread类,重写run方法

class MyThread extends Thread{
    @Override
    public void run() {
        while (true) {
            System.out.println("hello world");
            try {
                //不能throws
                //此处是方法重写,对于父类的run方法来说,没有throws xxx异常这样的说法
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

}


public class demo1 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();

        while(true){
            System.out.println("hello main");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  •  创建一个类,去继承Thread类,并且重写run方法

run方法:相当于线程的入口方法,线程具体跑起来之后,要做什么,都是通过run来描述的

start方法:创建线程,这个新创建出来的线程会参与到CPU的调度中.

               这个线程接下来要执行的工作,就是上面重写的run方法

(这个操作会在底层调用操作系统提供的"创建线程"的API,同时会在操作系统内核里创建出对应的pcb结构,并且加入到对应的链表中).

sleep方法: Thread的静态方法.这两个线程在执行sleep之后,就会进入阻塞状态,当时间到了之后,系统就会唤醒它们,恢复对这两个线程的调度.

                 当然,系统在进行多个线程调度时,并没有一个明确的顺序,而是按照"随机"的方式进行调度,即"抢占式执行"

问题:

可以直接调用run方法嘛? 不能!!!

myThread.run()

代码运行一下:

 

根据运行结果可知:

第一种代码,两个线程分别执行自己的循环,都能参与到CPU的调度中,这两个线程在并发的执行.

而第二种代码不会创建新的线程,仍然是在原来的主线程内部.

run只是上面的入口方法(普通的方法),并没有调用系统API,也没有创建出真正的线程

而start会调用系统API,在系统内核中把线程对应的pcb等等给创建出来并管理好,新的线程就能参与调度了

1.2 实现Runnable接口,重写run方法

class MyRunnable implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("hello xuexue");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
public class demo2 {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread t = new Thread(myRunnable);
        t.start();
        while (true){
            System.out.println("ppsb");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 创建一个类,实现Runnable 接口,并重写run方法
  • 创建 Thread 类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数.

Thread这里是直接要把完成的工作,放到了Thread的run方法里

Runnable这里则是分开了,把要完成的工作放到Runnable中,再让Runnable和Thread配合

1.3 基于匿名内部类,继承Thread,重写run方法

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

        t.start();
        while (true){
            System.out.println("hello hi");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 在demo3这个类里创建了一个子类,这个子类继承自Thread
  • 在子类中,重写了run方法
  • 创建了该子类的实例,并且使用t这个引用来指向

1.4 基于匿名内部类,实现Runnable,重写run方法

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");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        t.start();
        while (true){
            System.out.println("hihi");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}

  • 创建了一个Runnable的子类(实现Runnable)
  • 重写run方法
  • 把子类创建出实例,把这个实例传给Thread的构造方法

1.5 使用lambda表达式,实现run方法的内容

public class demo5 {
    public static void main(String[] args) {
        Thread t = new Thread(() ->{
            while (true){
                System.out.println("hello");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();
        
    }
}
  • lambad表达式,本质上就是一个"匿名函数",主要用来作为回调函数来使用

当然还可以基于Callable,基于线程池来创建线程

2.Thread的常见构造方法

方法说明

Thread()

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

 下面演示一下Thread(String name):

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

        t.start();
    }
}

 

 3.Thread的几个常见属性

属性获取方法
IDgetId()
名称

getName()

状态getState()
优先级getPriority()
是否后台线程isDaemon()
是否存活isAlive()
是否被终止isInterrupted()
  • ID:
  1. 线程的身份标识(在JVM这里给线程设定的标识).                                                                     
  2. 一个线程,可以有好几个身份标识,都是相互独立的.
  • 状态:
  1. Java中的线程状态,和操作系统中有一定差异
  • 优先级:
  1. 设置/获取优先级,作用并不是很大.                                                                                          (线程的调度主要还是系统内核来负责的,系统调度的速度太快了)
  • 是否后台线程:
  1. 前台线程会影响到进程结束,如果前台线程没执行完,进程是不会结束的                               
  2. 后台线程不影响进程结束.                                                                                                     
  3. 创建的线程,默认是前台线程.                                                                                                   
  4. 一个进程中所有的前台线程都执行完,退出了,及时此时存在后台线程仍未执行完,也会随着进程一起退出  
  • 是否存活:
  1. Thread对象的生命周期,并不是和系统中的线程完全一致的!!                                                   
  2. 一般是Thread对象先创建好,手动调用start,内核才能真正创建出线程.                                     
  3. 消亡的时候,可能是Thread对象先结束了生命周期(没有引用指向这个对象),也可能是Thread对象还在,内核中的线程把run执行完了,就结束了
  • 是否被终止:
  1. 一个线程的run方法执行完毕,就算终止了,此处的终止线程,就是想办法让run尽快的执行完毕

4.线程方法的使用

4.1 启动一个线程-start()

  • start方法调用系统的API完成线程创建工作
  • start方法本身的执行是一瞬间就完成的
  • 调用start方法后,代码会立即继续执行start后续的逻辑

4.2 终止一个线程

4.2.1 手动设定标志位

(通过这个手动设置的标志位,让run尽快结束)

第一种方式:

(通过内部类访问外部类的成员)

public class demo8 {
    //写作成员变量就不是触发变量捕获的逻辑了,而是"内部类访问外部类的成员"
    public static boolean isQuit = false;

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

        Thread t = new Thread(() ->{
            while (!isQuit){
                System.out.println("hello");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();

        //主线程这里执行一些其它逻辑之后,让t线程结束
        Thread.sleep(3000);

        //修改标志位
        isQuit = true;
        System.out.println("t线程结束");

    }
}

 第二种方式:

(lambda变量捕获)

问:

既然lambda表达式的执行时机是靠后的,这是否就导致,当后续真正执行lambda时,局部变量isQuit已经销毁了呢?

答:

这种情况是客观存在的,让lambda去访问一个已经被销毁的变量是明显不合理的.

所以lambda引入了"变量捕获"这样的机制.

lambda看起来是在内部访问外部的变量,其实本质上是把外部的变量给复制了一份到lambda里面(这样就可以解决刚才生命周期的问题了)

不过,变量捕获这里有个限制:

(虽然没有使用final修饰,但是并没在代码中修改变量,此时这个变量就可以视为是final)

如果这个变量想要进行修改,就不能进行变量捕获了

public static void main(String[] args) throws InterruptedException {
        boolean isQuit = false;

        Thread t = new Thread(() ->{
            while (!isQuit){
                System.out.println("hello");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        t.start();

        //主线程这里执行一些其它逻辑之后,让t线程结束
        Thread.sleep(3000);

        //修改标志位
        //isQuit = true;
        System.out.println("t线程结束");

    }

4.2.2 直接Thread类,提供好了现成的标志位,不用手动去设置了

Thread对象内部,提供了一个标志位

true:线程应该要结束

false:线程先不必结束

//线程终止,使用Thread自带的标志位
public class demo9 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() ->{
            //!Thread.currentThread()其实就是t
            //但是lambda表达式就是在构造t之前就定义好的,编译器看到的lambda里的t就会认为这是一个还没初始化的对象
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("jjj");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    //e.printStackTrace();
                    break;
                }
            }
        });
        t.start();
        Thread.sleep(3000);
        //把标志位改为true
        t.interrupt();
    }
}

下面是一些注意事项: 

 解释一下:t线程正在sleep,然后被interrupt给唤醒了

  • 线程在sleep过程中,其它线程调用interrupt方法,就会强制使sleep抛出一个异常,sleep就被立即唤醒了,sleep在被唤醒的同时,会自动清除前面设置的标志位,此时如果想继续让线程结束,直接在catch中,加个break即可
  • 当然,手动设置标志位,是没法唤醒sleep的

当sleep被唤醒后,接下来有以下几种操作方式:

  1. 立即停止循环,立即结束线程(break)
  2. 继续做点别的事,过一会再结束线程(catch中执行别的逻辑,执行完了再break)
  3. 忽略终止的请求,继续循环(不写break)

4.3 等待一个线程-join()

多个线程是并发执行的,具体的执行过程,都是由操作系统负责调度的!!!

而这个调度线程的过程是"随机的",无法确定,线程执行的先后顺序

等待线程,是一种规划线程结束顺序的手段

A,B两个线程,如果希望B先结束,让A线程中调用B.join()的方法,此时,B还没执行完,A线程就会进入"阻塞"状态(让代码暂时不执行了,该线程暂时不去CPU上参与调度),相当于给B留下了执行的时间,B执行完毕之后,A再从阻塞状态中恢复回来,并且继续往后执行,当然,如果A执行到B.join()时,B已经执行完了,A就不必阻塞了,直接往下执行即可

public class demo10 {
    public static void main(String[] args) {
        Thread b = new Thread(() ->{
            for (int i = 0;i < 5; i++){
                System.out.println("bbb");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("b结束了");
        });

        Thread a = new Thread(() ->{
            for (int i = 0; i < 3; i++){
                System.out.println("aaa");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                //如果b还没结束,a会产生堵塞
                b.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("a结束了");
        });

        b.start();
        a.start();
    }
}

一般来说更推荐此方法,有最大等待时间:

当然,join也能被interrupt唤醒,唤醒之后自动清除标志位 

4.4 获取当前线程引用

返回当前线程对象的引用

 上述代码中,Thread提供了静态方法currentThread,在哪个线程调用这个方法,就能够获取到哪个线程的引用

注意:

判定标志位:

Thread.interrupted()  静态方法,判定标志的同时会清除标志位,不可取

Thread.currentThread,isInterrupted() 成员方法,可取

4.5 休眠当前线程

在上述代码的实现过程中,对休眠当前线程的方法已经有了具体的认识,这里不过多解释

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

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值