JavaEE初阶 多线程 Thread(一)

文章目录

  • 1 第一个多线程程序
  • 2.创建线程
    • 2.1 继承Thread 重写run 基于匿名内部类
    • 2.2 实现Runnable 重写run 基于匿名内部类
    • 2.3使用lambda表达式 表示run方法的内容
  • 3.Thread常见的构造方法
    • 3.1 Thread的几个常见属性

1 第一个多线程程序

package thread;
class MyThread extends Thread{
    @Override
    //这里的run就相当于线程的入口方法 线程具体跑起来之后 要做啥事 都是通过run来描述的
    public void run(){
        System.out.println("hello world");
    }
}
//Thread 类来自java.lang这个包 不需要import就可以直接用
public class Demo1 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();//此处不仅仅要创建出类 还需要调用它让它执行起来
        myThread.start();
        //此处start就是在创建线程 这个操作就会在底层调用操作系统提供的"创建线程"API同时就会在操作系统内核里创建出对应的pcb结构并且加入到对应的链表中
        //此时 这个新创建出来的线程就会参与到cpu的调度中 这个线程接下来要执行的工作 就是刚刚上面重写的run方法
    }
}

Thread 类来自java.lang这个包 不需要import就可以直接用

这里的run就相当于线程的入口方法 线程具体跑起来之后 要做啥事 都是通过run来描述的

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

此时 这个新创建出来的线程就会参与到cpu的调度中 这个线程接下来要执行的工作 就是刚刚上面重写的run方法
在这里插入图片描述
当我们这样写的时候
在这里插入图片描述
run只是上面的入口方法(普通方法) 并没有调用系统api 也没有创建出真正的线程来

当点击运行程序的时候就会先创建出一个java进程这个进程中就包含了至少一个线程 这个线程也叫做主线程 也就是负责执行main方法的线程

当我们调整代码之后 在main方法中有一个while循环 在线程中run也有一个while循环
使用start的方式执行 此时这俩循环都在执行
在这里插入图片描述
在这里插入图片描述
两个线程分别执行之间的循环 这两个线程都能参与cpu的调度中以并发式执行

如果将代码改为
在这里插入图片描述
它只创建了一个线程 只有当run方法执行完成之后才会执行下面的代码 所以输出结果只有 hello world
在这里插入图片描述
总结
在这里插入图片描述

在这里插入图片描述
我们可以使用 jconsole查看线程
在这里插入图片描述
在这里插入图片描述
可以在jdk的路径下寻找
在这里插入图片描述
在这里插入图片描述
然后我们启动java程序
选择
在这里插入图片描述
能够列出系统上正在运行的所有的java进程
在这里插入图片描述
线程的调用栈
在这里插入图片描述
在这里插入图片描述
线程的调用栈 方法之间的调用关系
尤其是 当程序卡死了 查看一下 这里每个线程的调用栈 就可以知道大概那个代码出现卡死情况

Tread.sleep
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
Tread.sleep 时间是毫秒
运行结果并非是严格的交替
这两线程在进行sleep之后就会进入阻塞状态
当时间到之后 系统就会唤醒这两线程 回复线程的调度
谁先调度 可以认为是"随机"的
这样"随机"的调度过程 称位"抢占式执行"
在这里插入图片描述
Java中 通过Thread 类创建线程的方式还要很多种写法

  1. 创建一个类 继承 (class)Thread 重写run方法
  2. 创建一个类 实现(interface)Runnable 重写run方法 形容词性able结尾
    package thread;
    //使用Runable 的方式创建线程
class MyRunnable implements Runnable{

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

在这里插入图片描述
Thread 这里是直接把要完成的工作放到了Thread的run方法中
Runnable 这里 则分开了 要把完成的工作放到Runnable 中再让Runnable 和Thread配合
在这里插入图片描述

2.创建线程

2.1 继承Thread 重写run 基于匿名内部类

public class Deom3 {
    public static void main(String[] args) {
        Thread t = new Thread(){
            public void run() {
                while (true) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        };
        t.start();
        while (true){
            System.out.println("hello main");
            try{
                Thread.sleep(1000);
            }catch (InterruptedException e){
                e.printStackTrace();
            }
        }
    }
}
  1. 创建了一个子类 这个子类继承自Thread 但是这个子类没有名字(匿名)
  2. 在子类中 重写了run方法
  3. 创建了该子类的实例 并且使用t这个引用来指向

2.2 实现Runnable 重写run 基于匿名内部类

public class Demo {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                while (true){
                    System.out.println("hello world");
                    try {
                        Thread.sleep(1000);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        });//对于的是Thread构造方法的结束 new Runnable整个一段都是构造Thread的参数
    }
}
  1. 创建了一个Runnable的子类(类,实现Runnable)
  2. 重写了 run方法
  3. 把子类 创建出实例 把这个实例传给Thread的构造方法

2.3使用lambda表达式 表示run方法的内容

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

lambda 表达式 本质上就是一个"匿名函数"
这样的匿名函数主要可以用来回调函数来使用
回调函数 不需要程序员直接主动调用而是在合适的时机 自动的被调用
JavaEE初阶 多线程 Thread(一)_第1张图片
此处的这个回调函数 就是在线程创建成功之后才真正执行
Java 本身不允许函数脱离 类/对象 来使用 lambda相当于特殊情况
“函数式接口”
类似于lambda 这样的写法 本质上没有新增新的语言特性 而是把以往的实现的功能 换了一种更简洁的方式来编写

关于线程的创建 并没有这五种写法
基于Callable
基于线程池

3.Thread常见的构造方法

方法 说明
Thread 创建线程对象
Thread(Runnable target) 使用 Runnable 对象创建线程对象
Thread(String name) 创建线程对象,并命名
Thread(Runnable target, String name) 使用 Runnable 对象创建线程对象,并命名
【了解】Thread(ThreadGroup group,Runnable target) 线程可以被用来分组管理,分好的组即为线程组,这个目前我们了解即可

示例:

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

我们打开jconsole就可以看到我们命名的线程
JavaEE初阶 多线程 Thread(一)_第2张图片
这里并没有看到main线程是因为
JavaEE初阶 多线程 Thread(一)_第3张图片
一开始main线程执行遇到myThread线程执行lambda的死循环一直执行下去 然后当main执行完了 只剩下一个死循环继续执行
线程是通过start创建的 一个线程的入口方式执行完毕 对于主线程 是main 对于其他线程是run/lambda
示例2:

Thread t1 = new Thread();
Thread t2 = new Thread(new MyRunnable());
Thread t3 = new Thread("这是我的名字");
Thread t4 = new Thread(new MyRunnable(), "这是我的名字");

3.1 Thread的几个常见属性

属性 获取方法
ID getId()JVM给的身份标识
名称 getName()
状态 getState()
优先级 getPriority()
是否后台线程 isDaemon()
是否存活 isAlive()
是否被中断 isInterrupted()

在这里插入图片描述

  1. ID 是线程的唯一标识,不同线程不会重复

  2. 名称是各种调试工具用到

  3. 状态表示线程当前所处的一个情况,下面我们会进一步说明

  4. 优先级高的线程理论上来说更容易被调度到

  5. 关于后台线程(守护线程),需要记住一点:JVM会在一个进程的所有非后台线程结束后,才会结束运行。后台线程不影响进程结束 前台线程会影响进程结束 如果前台线程没执行完 进程是不会结束的 一个进程中所有前台都执行完了 退出此时后台线程仍然没执行完也会随着进程一起退出 创建的线程默认是前台线程
    示例:

package thread;

public class Demo7 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            while (true){
                System.out.println("hello world");
            }
        });
        //设置成后台线程
        t.setDaemon(true);
        t.start();
        try {
            Thread.sleep(300);

        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

JavaEE初阶 多线程 Thread(一)_第4张图片
当main线程结束时 被设置位守护线程也随之结束

  1. 是否存活,即简单的理解,为 run 方法是否运行结束了 Thread 对象 对应的线程(系统内核中)是否存活
    JavaEE初阶 多线程 Thread(一)_第5张图片
    返回true存在返回false不存在

  2. 线程的中断问题,下面我们进一步说明

start方法
在系统中 真正创建出线程
8. 创建出PCB
9. 把pcb加入对应的链表中(系统内核完成)

终止一个线程
一个线程的run方法执行完毕 就算终止了(结束)
此处的终止线程就是想办法让run能尽快执行完毕
正常情况下 不会出现 run 没执行完 线程突然就没了
10. 程序员手动设置标志位

 package thread;

public class Demo8 {
    public static boolean isQuit = false;
    //设置标志位
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            while (!isQuit) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        //主线程这里执行一些逻辑后 让thread线程结束
        thread.start();
        Thread.sleep(3000);
        //修改标志位
        isQuit = true;
        System.out.println("把t线程终止");
    }
} 

JavaEE初阶 多线程 Thread(一)_第6张图片
lambda变量捕获问题
当我们将 标识符写入main函数中时会报错 这是lambda变量捕获的问题
lambda 表达式 执行的时机时更靠后的
这就导致 后续真正执行lambda的时候局部变量isQuit就可能已经被销毁了
然后这时再让lambda去访问一个已经被销毁的变量明显时不合适的
lambda引入了"变量捕获"这样的机制
lambda内部看起来时直接访问外部的变量 其实本质上时把外部的变量 给复制了一份 到lambda里面 这样就可以解决生命周期的问题了
JavaEE初阶 多线程 Thread(一)_第7张图片
但是 变量捕获有限制
要求捕获的变量得是final (不能进行修改)
如果想让这个变量进行修改 就不能使用变量捕获了
JavaEE初阶 多线程 Thread(一)_第8张图片
写作成员变量就不是触发变量捕获的逻辑 而是"内部类访问外部类成员"
JavaEE初阶 多线程 Thread(一)_第9张图片

我们可以使用Thread类 给我们提供好了现成的标志位 不用自己手动去设置标志

public class Demo9 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(()->{
            //Thread.currentThread 就是t
            //但是lambda 表达式是在构造t之前就定义好的 编译器看到lambda 里的thread会认为是一个没有初始化的对象
            while (!Thread.currentThread().isInterrupted()){
                System.out.println("hellp thread");
                try{
                    Thread.sleep(1000);
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
        });
        thread.start();
        Thread.sleep(3000);
        thread.interrupt();
    }
}

在这里插入图片描述
Thread.currentThread 就是thread
但是lambda 表达式是在构造t之前就定义好的 编译器看到lambda 里的thread会认为是一个没有初始化的对象
获取到当前线程的对象 Thread提供了静态方法 currentThread在那个线程调用这个方法 就能获取到那个线程的引用
isInterrupted() true:结束线程
false 线程继续
thread.interrupt();将其设置为true

我们运行后会出现报错
在这里插入图片描述
thread线程正在sleep 然后被interrupt给唤醒(手动设置标识位是没办法唤醒的)
一个线程可能正在正常运行 也可能是在sleep
因此线程正在sleep过程中 其他线程调用interrupt方法中 就会强制使sleep抛出一个异常sleep就立即被唤醒了(假设你设定sleep(1000)虽然此处才过去10ms没到1000ms也会被立刻唤醒)
但是sleep在被唤醒的同时 会自动清除前面设置的标志位!
此时 如果我们先继续让线程结束 就直接在catch中加上break
JavaEE初阶 多线程 Thread(一)_第10张图片
JavaEE初阶 多线程 Thread(一)_第11张图片
如果不想看见异常信息可以把打印代码报错的注释掉
JavaEE初阶 多线程 Thread(一)_第12张图片
JavaEE初阶 多线程 Thread(一)_第13张图片
JavaEE初阶 多线程 Thread(一)_第14张图片

  1. 加入break
  2. catch中执行别的逻辑 执行完了再break
  3. 忽略终止的请求 继续循环
  4. JavaEE初阶 多线程 Thread(一)_第15张图片
    等待一个线程 join()
    多个线程使并发执行的 具体的执行过程 都是 由操作系统负责调度使随机的

JavaEE初阶 多线程 Thread(一)_第16张图片
示例:

public class Deom10 {
    public static void main(String[] args) {
        Thread b = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                System.out.println("hello b");
                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("hello a");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            try {
                b.join();
            }catch (InterruptedException e){
                e.printStackTrace();
            }
            System.out.println("a结束了");
        });
        a.start();
        b.start();
    }
}

如果此时b还没执行完毕b.join()会使b进行阻塞状态
JavaEE初阶 多线程 Thread(一)_第17张图片
JavaEE初阶 多线程 Thread(一)_第18张图片
JavaEE初阶 多线程 Thread(一)_第19张图片

你可能感兴趣的:(java-ee,java)