多线程篇二

多线程篇二

如笔者理解有误,欢迎交流指正⭐


Thread中重要属性

启动线程start

上篇提到 start 则是真正调用了系统 API, 在系统内核中创建出线程, 让线程再调用 run.(“并发“的实现)
再用代码举例下

// 创建一个类, 继承自 Thread .
class MyThread extends Thread {
    @Override
    public void run() {
        // 这个方法就是线程的入口方法.
        while (true) {
            System.out.println("Welcome Thread!");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

// 创建线程.
public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new MyThread();     
        //thread.start();  交替打印
        thread.run();
        //全部打印Welcome Thread!
        while (true) {
            System.out.println("Hello Main!");
            Thread.sleep(1000);
        }
    }
}
终止(中断)线程

顾名思义,isInterrupted存在的意义就是为了帮助我们停止正在运行的线程.

为什么要中断线程?

作为一个独立的单位,做一件事一定是有目的而且大多结果都是对自己有利,如果局面逐渐不可控制,甚至会造成损害那就有必要让其停下来或者直接取消对应的计划.
在java中我们引入中断的目的就是未了打断线程所处的某种状态(这种状态一定是阻塞状态)

中断的实现

要终止/销毁线程,就是要想办法让run方法尽快执行结束.

手动创建标志位

我们可以在代码中手动创建标志位,作为run执行结束的条件.
上代码套餐.

// 线程的打断
public class Demo8 {

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

        Thread thread = new Thread(() -> {
            while (!isQuit) {
                // 此处的打印可以替换成任意的逻辑来表示线程的实际工作内容
                System.out.println("线程工作中");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程工作完毕!");
        });

        thread.start();
        Thread.sleep(3000);

        isQuit = true;
        System.out.println("设置 isQuit 为 true");
    }
}

运行发现image-20240816205907546

为什么出现这样的情况?
小小lambda 大大的力量😀
isQuit此时是成员变量,而lambda有一个语法规则”变量捕获“.

变量捕获

lambda表达式会把当前作用域中的变量在lambda中cv一份(外面的变量是否销毁也就无关紧要了)
注意:变量捕获的前提是必须只能捕获一个final或者”实际上是final“的变量.(即初始化赋值后未改变值内容的“变量”)
image-20240816205923769
所以isQuit为成员变量时lambda捕获不到,就变成了**“内部类访问外部类的属性”**.就没有final的限制了.

// 线程的打断
public class Demo8 {
    private static boolean isQuit = false;

    public static void main(String[] args) throws InterruptedException {
         
        Thread thread = new Thread(() -> {
            while (!isQuit) {
                // 此处的打印可以替换成任意的逻辑来表示线程的实际工作内容
                System.out.println("线程工作中");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("线程工作完毕!");
        });

        thread.start();
        Thread.sleep(3000);

        isQuit = true;
        System.out.println("设置 isQuit 为 true");
    }
}
缺点

1.需要手动创建变量.
2.当线程内部sleep时,主线程修改变量,新线程不能及时响应.

调用interrupt()
// 线程终止
public class Demo9 {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            // Thread 类内部, 有一个现成的标志位, 可以用来判定当前的循环是否要结束.
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("线程工作中");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("让 t 线程终止. ");
        thread.interrupt();
    }
}

currentThread()获取当前线程的实例(Thread thread)
Thread内部有一个标志位用来判断线程是否结束
thread.interrupted()将Thread对象内部的标志位置为true(即使线程内部出现逻辑阻塞【sleep】也可以使用interrupted唤醒【使sleep内部触发一个异常 被提前唤醒】)
运行发现

image-20240816183840344

sleep确实被唤醒了但是线程仍在工作,并未真正结束.
注意:interrupted唤醒线程之后 ,sleep方法抛出异常,同时会自动清楚刚才设置的标志位
有三方式可以让我们有更多的“操作空间”.
上代码

// 线程终止
public class Demo9 {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
            // Thread 类内部, 有一个现成的标志位, 可以用来判定当前的循环是否要结束.
            while (!Thread.currentThread().isInterrupted()) {
                System.out.println("线程工作中");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // 1. 假装没听见, 循环继续正常执行.
                    e.printStackTrace();
                    // 2. 加上一个 break, 表示让线程立即结束.
                    // break;
                    // 3. 做一些其他工作, 完成之后再结束.
                    // 其他工作的代码放到这里.
                    break;
                }
            }
        });
        thread.start();

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("让 t 线程终止. ");
        thread.interrupt();
    }
}

附:

方法说明
public void interrupt()中断对象关联的线程,如果线程正在阻塞,则以异常方式通知, 否则设置标志位
public static boolean interrupted()判断当前线程的中断标志位是否设置,调用后清除标志位
public boolean isInterrupted()判断对象关联的线程的标志位是否设置,调用后不清除标志位
等待进程join

我们知道一个线程在完成它的工作后才能进行自己的下一步工作,但在期间想穿插别的工作进来就需要控制线程结束的顺序来实现.

public class Demo10 {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                System.out.println("线程工作中!");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
        thread.start();

        // 让主线程来等待 t 线程执行结束.
        // 一旦调用 join, 主线程就会触发阻塞. 此时 t 线程就可以趁机完成后续的工作.
        // 一直阻塞到 t 执行完毕了, join 才会解除阻塞, 才能继续执行
        System.out.println("join 等待开始");
        thread.join();
        System.out.println("join 等待结束");
    }
}
thread.join工作过程

1.如果线程正在工作中,此时调用thread线程就会阻塞,一直阻塞到thread线程执行结束位置.
2.如果thread线程已经执行结束了,此时调用join线程,就会直接返回,不会涉及阻塞.
【实际开发中比较建议使用join时带有超时时间】

Tips:
sleep本身也有精度误差,调度线程也有开销.(即唤醒线程之后【就绪状态】不是立即回到CPU上运行)

附:

方法说明
public void join()等待线程结束
public void join(long millis)等待线程结束,最多等millis毫秒
public void join(long millis,int nanos)精度更高但同理
获取线程引用
方法说明
public static Thread currentThread();返回当前线程对象的引用
public class ThreadDemo {
    public static void main(String[] args) {
        Thread thread = Thread.currentThread();
        System.out.println(thread.getName());
   }
}
休眠进程

眼熟吗老师们hh
注意:因为线程是不可控制的,所以这个方法只能保证实际休眠时间大于等于参数设置的休眠时间

方法说明
public static void sleep(long millis) throws InterruptedException休眠当前线程 millis毫秒
public static void sleep(long millis, int nanos) throws InterruptedException更高精度的休眠
public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        System.out.println(System.currentTimeMillis());
        Thread.sleep(3000);
        System.out.println(System.currentTimeMillis());
   }
}

线程状态

帮助我们快速判断当前程序执行的状况.
线程的状态是一个枚举类型Thread.State.

NEW

安排了工作但还在摸鱼未动
Thread对象已经有了但start方法还没调用.

TERMINATED

工作已完成
Thread对象还在,内核中线程已经结束了.

RUNNABLE

可工作,可以分成正在工作中和即将开始的工作
就绪状态(线程已经在CPU上执行了/线程正在等待CPU执行)

TIMED_WAITING

排队等安排工作
阻塞.sleep这种固定时间的方法产生的阻塞.

WAITING

排队等安排工作
由于wait这种不固定时间的方式产生的阻塞.

BLOCKED

排队等安排工作
阻塞.锁竞争产生.
与阻塞相关的三种状态后续常用于分析“线程卡死”问题

代码套餐
public class ThreadStateTransfer {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
            }
        }, "Thread");
        //线程未调用start方法对象已创建 此时状态为New
        System.out.println(t.getName() + ": " + t.getState());
        t.start();
        while (t.isAlive()) {
            //RUNNABLE 线程正在执行
            System.out.println(t.getName() + ": " + t.getState());
        }
        //线程执行结束TERMINATED
        System.out.println(t.getName() + ": " + t.getState());
    }
}

public class ThreadStateTransfer {
    public static void main(String[] args) {
        final Object object = new Object();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                    while (true) {
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }, "t1");
        t1.start();
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                    System.out.println("hehe");
                }
            }
        }, "t2");
        t2.start();
    }
}

image-20240816204151561

image-20240816204208543

使用jconsole观察,t1线程为TIMED_WAITING状态(排队等工作 sleep引起),t2线程为BLOCKED状态(由于t1未执行完 产生锁竞争)

修改sleep为wait后 t2线程结束 打印出“hehe” t1线程为WAITING状态

public class ThreadStateTransfer {
    public static void main(String[] args) {
        final Object object = new Object();
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                    while (true) {
                        try {
                            object.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }, "t1");
        t1.start();
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (object) {
                    System.out.println("hehe");
                }
            }
        }, "t2");
        t2.start();
    }
}

image-20240816204737122

结论

1.BLOCKED 表示等待获取锁, WAITING 和TIMED_WAITING 表示等待其他线程发来通知.
2.TIMED_WAITING 线程在等待唤醒,但设置了时限; WAITING 线程在无限等待唤醒


未完待续❀❀❀

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值