JavaEE初级——Thread多线程

多线程

认识线程

线程的概念

一个线程就是一个执行流,每一个线程都可以按照顺序执行自己代码,多个线程可以"同时"执行
多线程是并发编程的刚需

随着不断发展,单核CPU发展遇到了瓶颈,为了提高算力,就开始采用多核CPU,并发编程可以很好利用多核CPU

并且线程比进程更轻量 ,也程轻量级进程
创建 / 销毁 / 调度线程的效率多比进程快

创建线程

创建线程常用方法
继承 Thread, 重写 run
实现 Runnable, 重写 run
继承 Thread, 重写 run, 使用匿名内部类
实现 Runnable, 重写 run, 使用匿名内部类
使用 lambda 表达式

1.继承 Thread, 重写 run

java 复制代码
class MyThread extends Thread{
//重写其Thread中的run方法
    @Override
    public void run() {
        System.out.println("hello Thread");
    }
}
public class demo {
    public static void main(String[] args) {
        Thread t = new MyThread();


        t.start();
        System.out.println("hello main");
    }
}

2.实现 Runnable, 重写 run

java 复制代码
class MyRunnalbe implements Runnable{

    //实现run方法
    @Override
    public void run() {
        System.out.println("hello Thread");
    }
}
public class demo2 {
    public static void main(String[] args) {
        //传入Runnable对象
        MyRunnalbe myRunnalbe = new MyRunnalbe();
        Thread t = new Thread(myRunnalbe);

        t.start();
        System.out.println("hello main");
    }

}

3.继承 Thread, 重写 run, 使用匿名内部类

java 复制代码
//使用匿名内部类实现
public class demo3 {
    public static void main(String[] args) {
        Thread t = new Thread(){
            @Override
            public void run() {
                System.out.println("hello Thread");
            }
        };
        t.start();
        System.out.println("hello main");
    }
}

4.实现 Runnable, 重写 run, 使用匿名内部类

java 复制代码
public class demo4 {
    public static void main(String[] args) {
        Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello Thread");
            }
        });
        t.start();

        System.out.println("hello main");
    }
}

5.使用 lambda 表达式

java 复制代码
public class demo5 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
            System.out.println("hello Thread");
        });
        t.start();
        System.out.println("hello main");
    }
}

运行结果如下,但是其两个线程执行顺序是不确定的,所以结果有两种

一种先打印 hello main hello Thread,另一种就相反的顺序

t.start()是开启线程

java 复制代码
public class demo5 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
           while (true){
               System.out.println("hello Thread");
               try {
                   Thread.sleep(100);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
        });
        t.start();
       while (true){
           System.out.println("hello main");
           Thread.sleep(100);
       }
    }
}

上面这个代码是一直执行main和Thread这两个线程,并且执行顺序是不确定的,线程是和平常调用函数是不一样的,因为函数调用是会一直等函数执行完才可以执行下面的语句 ,因此如果这里是函数调用的话,只会出现一个值,要么一直打印hello Thread,要么是一直打印hello main

上面看似有5种,其实相当于三种,重写run方法,实现Runnable接口,使用lambda表达式

使用jdk目录下的bin目录中的的jconsole 来找出对应的线程文件,在这里面就可以找到我们创建的线程,此时main是线程,Thread-0也是我们创建的线程,由于我们创建的时候并没有给其命名,所以其系统自动命名为Thread - 数字

Thread类

Thread常见的构造函数

方法 说明
Thread() 创建线程对象
Thread(Runnable target) 使用Runnable对象创建线程对象
public Thread(String name) 创建线程对象,并命名
public Thread(Runnable target, String name) 使用Runnable对象创建线程对象,并命名
public Thread(Runnable target, String name) 线程分组管理
java 复制代码
public class demo6 {
    public static void main(String[] args) {
        Thread t1 = new Thread(()->{
            System.out.println(Thread.currentThread().getName());
        },"线程1");
        Thread t2 = new Thread(() ->{
            System.out.println(Thread.currentThread().getName());
        },"线程2");
        Thread t3 = new Thread(()->{
            System.out.println(Thread.currentThread().getName());
        },"线程3");

        t1.start();
        t2.start();
        t3.start();

        System.out.println("main");
    }
}

运行结果如下,但是顺序是不确定的,可以看出给线程命名成功

这里的**Thread.currentThread()**是获取当前Thread线程对象,**getName()**是获取当前线程的名称

Thread常见属性

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

ID :是线程的唯一标识,不同线程不会重复
名称 :就是线程的名字,这里可以自己命名,系统自动命名是Thread-数字
状态 :表示线程的情况,这里可以简单认为阻塞和就绪,细说的话有好几种状态
优先级 :我们可以给线程设置优先级,优先级高的先执行,但是对于像Windows、Lunix等高级系统,不能只看优先级,谁先执行决定权还是在操作系统
后台线程 :也称守护线程,后台线程不会阻断整个线程结束,而前台线程会阻断整个线程结束,并且所有非后台进程结束,才算真的结束
存活 :线程是否在执行,简单理解是run方法是否结束
中断 :停止某一个线程运行

下面进行详细讲解

getName()获取线程名,默认为Thread - 数字

java 复制代码
public class demo7 {
    public static void main(String[] args) {
        Thread t = new Thread(()->{
                //获取线程名
            System.out.println(Thread.currentThread().getName());
        });

        Thread t2 = new Thread(()->{
            //获取线程名
            System.out.println(Thread.currentThread().getName());
        });

        Thread t3 = new Thread(()->{
            //获取线程名
            System.out.println(Thread.currentThread().getName());
        });
        
        t.start();
        t2.start();
        t3.start();
        System.out.println("hello main");
    }
}

结果如下,其顺序是不确定的

isDaemon(),将一个线程设置为后台线程

java 复制代码
public class demo8 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(()->{
            for (int i = 0; i < 5; i++) {
                System.out.println("hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
//设置其为后端线程(守护线程)
        t.setDaemon(true);
        t.start();

        Thread.sleep(1000);

        System.out.println("主线程结束");
    }
}

可以看出这里的后端线程没有执行完,但是前台线程执行完了,此时就结束了
如果不设置后端线程,其t线程和main线程都执行完才结束

Thread中常用方法

1.休眠当前线程 参数是休眠时间,一个是休眠当前线程millis毫秒,一个更精确

方法 说明
public static native void sleep(long millis) 休眠当前线程millis毫秒
public static void sleep(long millis, int nanos) 同理,就是精度提高了

但是sleep(0)是特殊情况,属于线程主动放权,起到降低CPU的使用率,在资源紧张的时候,可能会进行一些合理的调整

中断一个线程

使用自己定义的一个标志位进行中断线程

java 复制代码
//中断一个线程
public class demo9 {
    private static boolean running = true;

    

public static void main(String[] args) {
        Thread t = new Thread(() ->{
           while (running){
               System.out.println("hello Thread");

               try {
                   Thread.sleep(1000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
            System.out.println("t线程结束");
        });

        t.start();

        Scanner sc = new Scanner(System.in);
        System.out.println("输入0,t线程结束");
        int n = sc.nextInt();
        if(n == 0){
            running = false;
        }
    }
}

运行结果如下

但是这个标识符要注意位置

像下面这样,将这个标识符放到main函数中,这样就会出现变量捕获

java中变量捕获,其实就是变量的一份拷贝,一旦拷贝了不一样可能会导致代码混乱,所以说java直接禁止了修改

但是可以像上面一样写成成员变量,这里就变成内部类访问外部类成员,这个是可以访问的

使用interrupted()和isInterrupted()来代替标志位

方法 说明
public void interrupt() 中断其对象关联的线程,如果线程阻塞,以异常的方式,反之设置标志位
public boolean isInterrupted() 判断当前线程中断标志位是否设置,不清理中断标志位
public static Thread currentThread() 返回当前线程对象的引用
java 复制代码
public class demo10 {
    public static void main(String[] args) {
        Thread t = new Thread(() -> {
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("hello Thread");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
        Scanner sc = new Scanner(System.in);
        System.out.println("输入0,阻断t线程");
        int n = sc.nextInt();
        if(n == 0){
            t.interrupt();
        }

    }
}

此时就会直接中断sleep

当线程因为wait/jioin/sleep方法引起阻塞,会以InterruptedException异常的形式通知,并中断标志,当然接不接书线程,主要还是取决于catch中是如何处理的

等待一个线程 join

方法 说明
public void join() 等待线程结束
public void join(long millis) 等待线程结束,最多等millis毫秒
public void join(long millis,int nanos 和上面一样,但是时间的精度更高

有时候需要等待一个线程结束,才进行其他步骤

java 复制代码
public class demo11 {
    private static int sum = 0;
    public static void main(String[] args) throws InterruptedException {
        //创建一个线程,计算1 ~ 1000
        Thread t = new Thread(() ->{
            for(int i = 1;i <= 1000;i++){
                sum += i;
            }
        });
        t.start();

        //这里main线程要等到其t线程执行完才可以执行
        t.join();
        System.out.println(sum);
    }
}

此时如果不等待t线程结束,拿此时main线程打印的sum值就不正确

线程的状态

状态 说明
NEW 安排了,工作还没做
RUNNABLE 可工作的,又可以分为正在工作和准备开始工作
TERMINATED 工作完成了,线程已经销毁,但Thread对象还在
WAITING 标识在等待,死等状态
TIMED_WAITING 有时间限制的等待
BLOCKED 等待,特指由锁引起的等待
java 复制代码
public class demo12 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() ->{
            System.out.println("Thread:"+Thread.currentThread().getState());
        });
        System.out.println(t.getState());
        t.start();

        Thread.sleep(1000);
        System.out.println("main:"+t.getState());
    }
}

此时先打印NEW,此时t线程都没有启动,t需要等到start才能执行

启动后 RUNNABLE 正在执行或准备执行,最后TERMINATED,说明已经执行完了

java 复制代码
public class demo12 {
    public static void main(String[] args) throws InterruptedException {
        //获取main的线程对象
        Thread mainThread = Thread.currentThread();
        Thread t = new Thread(() ->{
            while (true){
                System.out.println("mainThread:"+mainThread.getState());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }
        });
        System.out.println(t.getState());
        t.start();
        t.join();

        System.out.println(t.getState());
    }
}

此时main线程要等待t线程执行完毕,并且这里是死等,因此t线程在执行时候,mainThread状态是 WAITING

java 复制代码
public class demo12 {
    public static void main(String[] args) throws InterruptedException {
        //获取main的线程对象
        Thread mainThread = Thread.currentThread();
        Thread t = new Thread(() ->{
            while (true){
                System.out.println("mainThread:"+mainThread.getState());
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }

            }
        });
        System.out.println(t.getState());
        t.start();
        t.join(100000);

        System.out.println(t.getState());
    }
}

有时间限制的等待

当然使用sleep也是同样的效果

相关推荐
JH30737 小时前
SpringBoot 优雅处理金额格式化:拦截器+自定义注解方案
java·spring boot·spring
Coder_Boy_9 小时前
技术让开发更轻松的底层矛盾
java·大数据·数据库·人工智能·深度学习
helloworldandy9 小时前
使用Pandas进行数据分析:从数据清洗到可视化
jvm·数据库·python
invicinble9 小时前
对tomcat的提供的功能与底层拓扑结构与实现机制的理解
java·tomcat
较真的菜鸟9 小时前
使用ASM和agent监控属性变化
java
黎雁·泠崖9 小时前
【魔法森林冒险】5/14 Allen类(三):任务进度与状态管理
java·开发语言
qq_124987075311 小时前
基于SSM的动物保护系统的设计与实现(源码+论文+部署+安装)
java·数据库·spring boot·毕业设计·ssm·计算机毕业设计
Coder_Boy_11 小时前
基于SpringAI的在线考试系统-考试系统开发流程案例
java·数据库·人工智能·spring boot·后端
Mr_sun.11 小时前
Day06——权限认证-项目集成
java
瑶山11 小时前
Spring Cloud微服务搭建四、集成RocketMQ消息队列
java·spring cloud·微服务·rocketmq·dashboard