• JavaEE——Thread类


    JavaEE传送门

    JavaEE

    JavaEE——进程调度

    JavaEE——进程与线程的关系



    Thread类的基本用法

    1.创建线程

    #继承Thread类

    1)继承Thread类来创建一个线程类

    class MyThread extends Thread {
        //重写父类Thread中的run方法,run里面的逻辑,就是这个线程需要执行的工作
        @Override
        public void run() {
            System.out.println("hello thread");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    2)创建MyThread类的实例,调用start方法启动线程

    public class Demo1 {
        public static void main(String[] args) {
            MyThread t = new MyThread();//创建一个实例,并不会再系统中真的创建一个线程
            t.start();//线程开始运行,调用start方法的时候才真正创建出一个新的线程 
            System.out.println("hello main");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    # 注意事项 #

    1. 运行一次Java程序,就是启动了一个进程
    2. 一个进程里至少会有一个线程,默认的线程,正是main方法所在的线程(也叫做主线程)
    3. main主线程,和MyThread创建出来的新线程,是一个"并发执行"的关系(“并发= 并发 + 并行”)
    4. 操作系统调度线程的时候,是一个"随机"的过程,main线程和和MyThread创建出来的新线程谁先打印是随机的

    一个多线程程序

    class MyThread extends Thread {
        @Override
        public void run() {
            while(true) {
                System.out.println("hello thread");
                try {
                    Thread.sleep(1000);//sleep表示"休眠",让线程阻塞1000ms
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    public class Demo1 {
        public static void main(String[] args) {
            MyThread t = new MyThread();
            t.start();
    
            while(true) {
                System.out.println("hello main");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    运行该程序,我们可以看到打印的顺序是随机的,是不可预测的

    20200811130123_5074f

    使用jconsole命令观察线程

    找到jconsole.exe,双击打开

    选择本地进程,选择刚刚写的Demo1,点击链接,选择不安全的链接

    点击线程,我们可以看到左下角有许多线程


    #实现Runnable接口

    1)实现Runnable接口

    class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("hello thread");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2)创建Thread类实例, 调用 Thread 的构造方法时将 Runnable 对象作为 target 参数, 并调用 start 方法.

    public class Demo2 {
        public static void main(String[] args) {
            MyRunnable runnable = new MyRunnable();
            Thread t = new Thread(runnable);
            t.start();
    
            System.out.println("hello main");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    那么这样写的好处是什么呢?

    将任务提取出来, 目的是为了解耦合.

    上面一种继承Thread写法, 就把线程要完成的工作和线程本身, 耦合在一起了, 假设未来要对这个代码进行调整(不用多线程了), 代码改动就会比较大.

    如果想搞多个线程, 都干一样的活, 这时也是更合适使用Runnable的.

    20200811130123_5074f

    对比上面两种方法

    继承Thread类实现Runnable接口
    工作和线程本身耦合在一起低耦合
    直接使用 this 就表示当前线程对象的引用this 表示的是 MyRunnable 的引用. 需要使用 Thread.currentThread()

    #匿名内部类创建Thread子类对象

    public class Demo3 {
        public static void main(String[] args) {
            Thread t = new Thread() {
                @Override
                public void run() {
                    System.out.println("hello thread");
                }
            };
    
            t.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    #匿名内部类创建 Runnable 子类对象

    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();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    #lambda 表达式创建 Runnable 子类对象

    public class Demo5 {
        public static void main(String[] args) {
            Thread t = new Thread(() -> {
                System.out.println("hello thread");
            });
    
            t.start();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    2.Thread的几个常见属性

    属性获取方法
    IDgetId()
    名称getName()
    状态getState()
    优先级getPriority()
    是否后台线程isDaemon()
    是否存活isAlive()
    是否被中断isInterrupted()
    • ID是Java中给Thread对象安排的身份标识, 身份标识可以有多个, 在不同的环境下, 使用不同的标识(比如说一个线程在JVM中有一个id, 在操作系统的线程API中有一个id, 在内核PCB中还有一个id)

    • 名称是在构造方法中, 指定的name, 在各种调试工具中用到(比如说jconsole)

    • getState()获取线程的状态(下一篇博客会讲解)

    • getPriority()获取线程优先级

    • isDaemon()判断该线程是否是守护线程(后台线程), 我们默认创建的线程是 "前台线程" (前台线程会阻止进程退出, 如果main运行完了, 前台线程还没完, 进程不会退出). 如果是后台线程, 后台线程不阻止进程退出(如果main等其他前台线程执行完了, 即使后台线程没执行完,进程也会退出)

      我们可以用setDaemo将默认进程设置成后台线程, 设置操作需要在start之前, 如果线程启动了, 就改不了

      t.setDaemon(true);
      
      • 1
    • 是否存活,简单的理解,即为 run 方法是否运行结束了

    • 线程中断的问题, 下面进一步说明

    public class Demo7 {
        public static void main(String[] args) {
            Thread t = new Thread(() -> {
                while(true){
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            },"我的Thread");
    
            t.start();
    
            System.out.println(t.getId());//ID
            System.out.println(t.getName());//名称
            System.out.println(t.getPriority());//优先级
            System.out.println(t.getState());//状态
            System.out.println(t.isDaemon());//是否后台线程
            System.out.println(t.isAlive());//是否存活
            System.out.println(t.isInterrupted());//是否被中断
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    2.启动程序-start()

    调用 start 方法, 才真的在操作系统的底层创建出一个线程.

    如果我们不调用start方法, 只调用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) {
            MyThread t = new MyThread();
            t.run();
            //t.start();
    
            while(true) {
                System.out.println("hello main");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    运行结果展示, 我们的运行结果只有"hello thread", 并没有"hello main", 只调用run方法并没有创建线程, 他只是在main这个线程里调用run方法.


    3.线程中断

    run方法执行完, 线程就结束了. 那我们有什么办法可以让线程提前结束呢?

    通过线程中断的方法来进行, 其本质仍是让run方法尽快结束, 而不是run执行一半, 强制结束.

    1)自己定义一个标志位, 作为线程结束的标志

    public class Test {
    	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();
                   }
               }
                System.out.println("t线程执行完了");
            });
    
            t.start();
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            isQuit = true;
            System.out.println("设置让t线程结束!");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    2)使用标准库中自带的标志位, 在主线程中,通过t.interrupt中断线程

    public class Demo8 {
        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) {
                       e.printStackTrace();
                   }
               }
                System.out.println("t线程执行完了");
            });
    
            t.start();
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t.interrupt();//中断线程,设置标志位为true
            System.out.println("设置让t线程结束!");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    运行该程序, 我们可以看到触发了异常, 但线程仍在运行

    在这里插入图片描述
    那么这是为什么呢?

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

    1. t 线程在运行状态.会设置标志位为true
    2. t 线程在阻塞状态(比如说sleep), 不会设置标志位, 而是触发一个InterruptedException, 这个异常会把sleep提前唤醒

    由于代码中只是打印了日志, 并没有结束循环, 因此线程还是在继续执行, 所以我们需要由线程自身的代码来判定处理.

    Thread t = new Thread(() -> {
    	while(!Thread.currentThread().isInterrupted()) {
           System.out.println("hello thread");
           try {
               Thread.sleep(1000);
           } catch (InterruptedException e) {
               e.printStackTrace();
    
               1.立即结束进程
               break;
    
               //2.不做理会,线程继续执行
    
               //3.线程稍后处理
               //Thread.sleep(1000);
               //break;
           }
       }
        System.out.println("t线程执行完了");
    });
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    4.线程等待-join()

    线程之间的调度顺序, 是不确定的. 我们可以通过一些特殊手段, 来对线程的执行顺序做出干预.

    join方法, 可以控制线程之间的结束顺序

    在main中调用t.join方法, 让main阻塞等待, 等到 t 执行完了, main才继续执行

    public class Test {
        public static void main(String[] args) {
            Thread t = new Thread(() -> {
                for (int i = 0; i < 5; i++) {
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
            System.out.println("main线程 join之前");
            try {
                t.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("main线程 join之后");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    运行结果展示

    # 注意 # 在调用 jion 之前, 若 t 线程已经结束了, 此时 join 不需要阻塞等待

    20200811130123_5074f

    join的几种版本

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

    5.休眠线程-sleep()

    在操作系统组织这些 线程的 PCB的时候, 是会有多个链表的.

    A线程调用了 sleep .

    则这个PCB 就会被移动到另外的 “阻塞队列” 中.

    当A线程, sleep 的时间到了, 就会被移动到之前的就绪队列

    # 注意 # 移回就绪队列, 不代表立即就能够上CPU执行. 还得看系统啥时候调度这个线程.(sleep(1000), 不一定是只休眠了1000, 一般要略多于1000)


    6. 获取当前线程引用

    public static Thread currentThread();//返回当前线程对象的引用
    
    • 1

    🌷(( ◞•̀д•́)◞⚔◟(•̀д•́◟ ))🌷

    以上就是今天要讲的内容了,希望对大家有所帮助,如果有问题欢迎评论指出,会积极改正!!

    在这里插入图片描述
    加粗样式

    这里是Gujiu吖!!感谢你看到这里🌬
    祝今天的你也
    开心满怀,笑容常在。
  • 相关阅读:
    Web3.0与区块链有何不同?现在处于哪个阶段?
    PID控制电机输出作为电机PWM占空比输入的理解
    el-tree实现表格方式菜单
    ValueError: --caption_column‘ value ‘text‘ needs to be one of: image
    基于SSM+Jsp的书店仓库管理系统
    【备忘录】JAVASDK连接MinIO,附完整代码
    Android学习笔记5 - 初学Activity(二)多个、生命周期、启动模式
    Vue--》简述组件的数据共享
    重学操作系统(二)进程管理
    微信第三方sdk获取openid 报错Caused by: java.lang.NoClassDefFoundError
  • 原文地址:https://blog.csdn.net/m0_58592142/article/details/126809668