=–=–=–=–=–=–=–=–=
多线程原理详解【中】
静态代理模式(与多线程的关系演示)
静态代理模式就是线程的一个底部的实现原理,通过这个结婚案例 来 演示静态代理和多线程之间的关系:
演示静态代理 对比 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,这个非守护线程停止执行☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆☆");
}
}