多线程原理详解【中】(静态代理模式案例演示、线程停止、线程睡眠 sleep、线程礼让 yield、线程强制执行 join 、线程状态 state、优先级 priority、守护线程 daemon)

=–=–=–=–=–=–=–=–=

多线程原理详解【上】

多线程原理详解【中】


多线程原理详解【下】


静态代理模式(与多线程的关系演示)


静态代理模式就是线程的一个底部的实现原理,通过这个结婚案例演示静态代理和多线程之间的关系:

在这里插入图片描述


演示静态代理 对比 Thread

如图,可以看到这个静态代理的例子,和之前演示多线程时,通过创建一个Thread线程对象,把自定义的线程类对象作为参数传递进去,让 Thread 线程对象调用 start() 方法,去实现自定义线程类对象里面的 run() 方法是一样的。
都是用到了代理模式。

因为静态代理模式就是线程的一个底部的实现原理

静态代理模式总结:
真实对象和代理对象都要实现同一个接口
代理对象要代理真实角色

好处:
代理对象可以做很多真实对象做不了的事情
真实对象专注做自己的事情即可

在这里插入图片描述


分析静态代理模式案例的逻辑

在这里插入图片描述


具体代码


//静态代理模式总结:
//真实对象和代理对象都要实现同一个接口
//代理对象要代理真实角色

//好处:
//代理对象可以做很多真实对象做不了的事情
//真实对象专注做自己的事情即可
public class ProxyStatic {

    public static void main(String[] args) {
        You you = new You();
        //如果不用代理模式,就是自己调用结婚方法
        //you.HappyMarry();

        //使用代理模式,把自己交给婚庆公司即可
        new WeddingCompany(you).HappyMarry();

        //和多线程的代理对比
        new Thread(() -> System.err.println("结婚")).start();

    }
}

//接口
interface Marry { void HappyMarry();}

//真实角色-->结婚本人
class You implements Marry {
    //实现接口方法
    @Override
    public void HappyMarry() {
        System.err.println("真实角色--你本人结婚了");
    }
}

//代理角色-->帮助你结婚
class WeddingCompany implements Marry {

    //代理目标对象 -- 结婚对象
    private Marry target;
    //构造器
    public WeddingCompany(Marry target) {
        this.target = target;
    }
    //实现接口方法
    @Override
    public void HappyMarry() {
        //结婚之前的方法
        before();
        //这个就是真实对象
        this.target.HappyMarry();
        //结婚之前的方法
        after();
    }
    //结婚之前的方法
    private void before() {
        System.err.println("代理对象--婚庆公司帮你结婚之前布置现场");
    }
    //结婚之前的方法
    private void after() {
        System.err.println("代理对象--婚庆公司在你结婚之后收尾款");
    }
}



线程五大状态


5大状态解释

在这里插入图片描述


在这里插入图片描述

NEW:创建状态

当我们使用 new 操作符创建一个线程对象时,该线程处于 New 状态。在这种状态下,线程对象已经被创建,但是还没有被启动。
在这个状态下,线程对象还没有和 CPU 时间片关联起来,CPU 还没有对这个线程进行调度,也就是说,它还没有执行 run() 方法。

RUNNABLE:就绪状态

在Java线程的生命周期中,RUNNABLE状态表示线程已经被启动并正在等待CPU时间片来执行代码。在这种状态下,线程正在运行或准备运行,但可能被挂起以让其他线程运行。

在RUNNABLE状态下,线程已经准备好执行代码,并且可以被操作系统调度为运行状态。当线程被调度并获得CPU时间片时,它将进入运行状态,并在自己的线程栈上执行代码。

Java线程的RUNNABLE状态其实是包括了传统操作系统线程的ready和running两个状态的。

TIMED_WAITING:定时等待状态

在Java线程的生命周期中,TIMED_WAITING状态表示线程正在等待某个特定时间内的某个事件发生,例如休眠(sleep)、等待(wait)、加锁(lock)等待超时、定时器(timer)等待、IO等待等操作。

当一个线程在调用Thread.sleep()、Object.wait()、Lock.tryLock(long timeout, TimeUnit unit)等方法时,线程将进入TIMED_WAITING状态,并在指定的时间内等待相应的事件发生。

TERMINATED:线程终止状态

线程正常执行完毕或者出现异常终止,就会进入这个状态。

在Java线程的生命周期中,TERMINATED状态是线程的最终状态,表示线程已经执行完毕并已经退出。当一个线程完成了它的工作,或者因为异常而提前结束时,它会进入TERMINATED状态。此时线程不再执行任何代码,并且无法回到任何其他状态。

"TERMINATED"和"DEAD"都表示线程已经终止,不再运行,但"TERMINATED"是Java线程的一种具体状态,而"DEAD"则是对终止线程的更通用的描述。

一些线程方法

在这里插入图片描述


线程的一些操作


1、线程停止 -> 自定义标识


1-1:演示自定义方法,通过标识切换来停止子线程的执行

在这里插入图片描述

在主线程弄个循环,当 i 值达到 95 时,调用停止线程的方法,修改标识,让子线程停止执行。

在这里插入图片描述


具体代码:
package cn.ljh.threaddemo.stopthread;

//测试停止线程的操作
//1、建议线程正常停止 --> 利用次数,不建议死循环
//2、建议使用标志位 --> 设置一个标志位
//3、不要使用 stop 或者 destroy 等过时或者 JDK 不建议使用的方法
public class TestStopThread implements Runnable {

    //1、设置一个标识位
    private boolean flag = true;

    @Override
    public void run() {
        int i = 0;
        while (flag){
            System.err.println("子线程:循环执行第【 "+ i++ +" 】个任务");
        }
    }

    //自定义一个线程停止的方法,修改标识
    public void customStopThread(){
        this.flag = false;
    }

    public static void main(String[] args) {

        //创建一个自定义线程对象作为代理对象
        TestStopThread t = new TestStopThread();
        //创建一个线程对象,调用 start 方法启动新线程
        new Thread(t).start();

        for (int i = 0; i < 100; i++) {
            System.err.println("主线程循环第:" + i + " 次");
            if (i == 95){
                //调用自定义停止线程的方法,切换标识,让线程停止
                t.customStopThread();
                System.err.println("停止子线程的执行============================");
            }
        }

    }

}

2、线程睡眠 -> sleep()


2-1:通过 sleep() 模拟网络延时

这个很简单,就线程对象Thread 调用 sleep() 方法而已,代码是之前的代码

在这里插入图片描述


具体代码
package cn.ljh.threaddemo.sleep_thread;


import cn.ljh.threaddemo.demo01.TestThread05;

//模拟网络延时:作用:放大问题的发生性
public class TestSleepThread implements Runnable {

    //票数
    private int ticketNums = 10;

    @Override
    public void run() {
        while (true) {
            synchronized (this) {
                if (ticketNums <= 0) {
                    //终止当前所在循环
                    break;
                }
                System.err.println(Thread.currentThread().getName()
                        + "--->买到了第【 " + ticketNums-- + " 】张票");
            }

            //模拟延迟---1秒等于1000毫秒
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }

    public static void main(String[] args) {
        TestThread05 t = new TestThread05();
        //启动新线程  参数1:t 就是代理对象   参数2:name 就是当前线程名字
        Thread t1 = new Thread(t, "小黄");
        Thread t2 = new Thread(t, "小白");
        Thread t3 = new Thread(t, "小绿");

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

    }

}

2-2:通过 sleep 模拟倒计时和定时获取当前时间

在这里插入图片描述


在这里插入图片描述


具体代码
package cn.ljh.threaddemo.sleep_thread;


import lombok.SneakyThrows;

import java.text.SimpleDateFormat;
import java.util.Date;

//使用 sleep 模拟倒计时
public class TestSleepThread02 {

    public static void main(String[] args) {
        countDown(10);
        currentTime();
    }

    //倒计时的方法
    @SneakyThrows
    public static void countDown(int num) {
        while (true) {
            Thread.sleep(1000);
            System.err.println("倒计时:" + num--);
            if (num<=0){
                break;
            }
        }
    }

    //打印当前系统时间方法
    @SneakyThrows
    public static void currentTime(){
        //获取系统当前时间
        Date nowTime = new Date(System.currentTimeMillis());
        while (true){
            Thread.sleep(1000);
            //格式化时间
            System.err.println(new SimpleDateFormat("HH:mm:ss").format(nowTime));
            //更新当前时间
            nowTime = new Date(System.currentTimeMillis());
        }
    }
    
}

3、线程礼让 -> yield

比如有 A 、B 两个线程都处于就绪状态,等待 CPU 调度,然后 CPU 调度了让 A线程执行,但是 A 线程在执行的时候,想让给 B 线程来执行,所以A线程就从运行状态转为就绪状态,然后再重新和B线程竞争。

这个礼让不是说下一次CPU就一定会调度B线程去执行,而是让两个线程重新竞争,也许A线程礼让后,重新竞争又是A线程竞争到。

在这里插入图片描述


3-1:演示两个线程进行礼让操作

在这里插入图片描述


具体代码
package cn.ljh.threaddemo.yield_thread;

import lombok.SneakyThrows;

//测试线程礼让
public class TestYieldThread {
    @SneakyThrows
    public static void main(String[] args) {
        MyYield t = new MyYield();
        new Thread(t,"线程A").start();
        //Thread.sleep(1000);
        new Thread(t,"线程B").start();

    }
}

//创建一个线程类
class MyYield implements Runnable{

    @Override
    public void run() {
        System.err.println(Thread.currentThread().getName()+" --> 开始执行");
        //线程礼让
        Thread.yield();
        System.err.println(Thread.currentThread().getName()+" --> 停止执行");
    }
}

4、线程强制执行 -> join

Join 合并线程,待此线程执行完成后,再执行其他线程,其他线程阻塞。

join 方法的主要作用就是同步,它可以使得线程之间的并行执行变为串行执行。
比如在A线程中调用了B线程的 join() 方法时,表示只有当 B 线程执行完毕时,A线程才能继续执行。

可以想象成插队,A线程在执行的过程中,B线程调用 join() 方法突然插队进来,并且需要等B线程执行完后,A线程才能继续执行。

使用 join() 方法会造成线程阻塞。


4-1:演示B线程插队A线程

这里【 Thread thread = new thread(t) 】只new 一个子线程来演示,如果需要多个线程演示

【 Thread t1 = new thread(t) 】
【 Thread t2 = new thread(t) 】
【 Thread t3 = new thread(t) 】

这样创建多个子线程出来,要在某个线程让 t3 插队的话,只需要在某个线程里面添加 t3.join() 即可

在这里插入图片描述


具体代码
package cn.ljh.threaddemo.join_thread;


//测试 join 方法
public class TestJoinThread implements Runnable {
    
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.err.println("线程B来了-----------> " + i);
        }
    }

    public static void main(String[] args) throws InterruptedException {

        //创建自定义线程类对象
        TestJoinThread t = new TestJoinThread();
        //创建线程对象
        Thread thread = new Thread(t);
        //启动新线程
        thread.start();

        for (int i = 0; i < 50; i++) {
            if (i == 25) {
                //线程B插队
                thread.join();
            }
            System.err.println("主线程A正在执行=========> " + i);
        }
    }
}

5、线程状态观测 -> state

在这里插入图片描述


NEW 创建状态:
线程创建完成,但是还没被 start 方法调用启动

runnable 就绪状态:
线程对象调用start方法后,线程进入该状态,等待被CPU调度执行

timed_waiting 定时等待状态:

这里是Thread线程对象调用sleep()方法,让子线程进入定时等待状态。

当一个线程在调用Thread.sleep()、Object.wait()、Lock.tryLock(long timeout, TimeUnit unit) 等方法时,线程将进入TIMED_WAITING状态,并在指定的时间内等待相应的事件发生。

terminated 线程结束状态
线程正常执行完毕,或者遇到异常提前结束,就会进入这个状态。


5-1:演示观测线程的几个状态

在这里插入图片描述


注意点:
线程执行完毕后,是不能再次调用 start() 方法启动的。
线程是不能启动两次的,一个线程对象只能启动一次,否则报错。

在这里插入图片描述


具体代码
package cn.ljh.threaddemo.state_thread;


//观测线程状态
public class TestStateThread {

    public static void main(String[] args) throws InterruptedException {

        //直接用 lambda 表达式,省略在类名后面显式地实现Runnable接口
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.err.println("线程执行结束............");
            }
        });

        //观察线程启动之前的线程状态
        Thread.State state = thread.getState();
        System.err.println("线程状态--> "+state);

        //观察线程启动之后的线程状态
        thread.start(); // 启动线程
        state = thread.getState();
        System.err.println("线程状态--> "+state);

        //只要子线程不终止,就一直更新线程状态
        while (state != Thread.State.TERMINATED){
            //每隔100毫秒,更新一次线程状态
            Thread.sleep(100);
            state = thread.getState();
            System.err.println("线程状态--> "+state);
        }
    }

}


6、线程的优先级 -> priority

优先级默认是 5 ;
优先级最高是10;最低是1;
设置优先级为10,则该线程被CPU调度到的概率最大。

在这里插入图片描述


在这里插入图片描述


6-1:演示线程的优先级

主线程,默认的优先级是【5】
在这里插入图片描述


线程的优先级演示

在这里插入图片描述


具体代码
package cn.ljh.threaddemo.priority_thread;

//测试线程的优先级
public class TestPriorityThread {
    public static void main(String[] args) {

        System.err.println("当前线程:【 " + Thread.currentThread().getName()
                + " 】,优先级为:【 " + Thread.currentThread().getPriority() + " 】");

        //new 一个自定义的线程类对象
        MyPriority t = new MyPriority();

        //创建线程类对象
        Thread t1 = new Thread(t,"t1");
        Thread t2 = new Thread(t,"t2");
        Thread t3 = new Thread(t,"t3");
        Thread t4 = new Thread(t,"t4");
        Thread t5 = new Thread(t,"t5");
        Thread t6 = new Thread(t,"t6");
        Thread t7 = new Thread(t,"t7");

        //先设置优先级,再启动
        t1.start();

        t2.setPriority(1); //设置优先级
        t2.start(); //启动线程


        t3.setPriority(4);
        t3.start();

        t4.setPriority(Thread.MAX_PRIORITY); //MAX_PRIORITY = 10
        t4.start();

        t5.setPriority(-1);
        t5.start();

        t6.setPriority(0);
        t6.start();

        t7.setPriority(11);
        t7.start();
    }
}

//创建一个线程类
class MyPriority implements Runnable {

    @Override
    public void run() {
        System.err.println("当前线程:【 " + Thread.currentThread().getName()
                + " 】,优先级为:【 " + Thread.currentThread().getPriority() + " 】");
    }
}

7、守护线程 -> daemon

用户线程,比如 main 主线程
守护线程,比如 gc 垃圾回收线程

有 A、B 两个线程,把A设置成守护线程,那么只要B线程执行完,那么A线程也会停止执行。

当线程A被设置为守护线程(Daemon Thread)时,只要所有非守护线程(比如线程B)执行完毕并且主程序(通常是main线程)退出,JVM 就会自动退出,此时守护线程A也会被强制终止,不管它是否执行完毕。

守护线程(Daemon Thread)通常被用来提供后台服务或执行一些辅助性的任务,它们不应该执行关键性的任务或持有重要资源,因为它们的生命周期是依赖于非守护线程的。

在这里插入图片描述

JVM 不用等待守护线程执行完毕,因为主要是保证用户线程能正常执行,像是 gc 这种守护线程,JVM是不需要等待它执行完毕的。


在这里插入图片描述


7-1:测试守护线程

在这里插入图片描述


具体代码
package cn.ljh.threaddemo.daemon_thread;

//测试守护线程
public class TestDaemonThread {

    public static void main(String[] args) {
        God god = new God();
        People people = new People();

        //创建一个新线程
        Thread g = new Thread(god);
        //把 God 线程设置为守护线程
        //true表示该线程为守护线程; 默认是false 非守护线程,就是用户线程,正常的线程都是用户线程
        g.setDaemon(true);
        //启动线程
        g.start();

        //非守护线程,也就是用户线程-----------
        Thread p = new Thread(people);
        p.start();
    }
}

//创建一个线程类----神仙-----作为守护线程
class God implements Runnable{
    @Override
    public void run() {
        //守护线程通常会一直执行,直到非守护线程全部结束或者 JVM 退出,守护线程才会终止
        //所以这里用while循环为true一直执行下去
        while (true){
            System.err.println("守护线程------------------------");
        }
    }
}

//创建一个线程类----人-----作为非守护线程
class People implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.err.println("人类生活第【 "+ i +"】年");
        }
        System.err.println("100年后gg,这个非守护线程停止执行☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆");
    }
}

  • 27
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_L_J_H_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值