背景
不知道菜虚鲲是什么?也许你可以在休闲时间里看看这个视频。你只需要知道,菜虚鲲和视频里的小哥很像,都喜欢唱,跳, rap 和 篮球。这个舞蹈的名称叫做《鸡你太美》,其中的篮球技巧非常的抓人眼球,后面的战术抖肩带也令人回味无穷。好了,背景知识介绍完毕。(以下内容相当不严谨,只为帮助大家理解多线程)
线程与进程
进程就像一个简化版的B站,里面有很多素材(线程)上演各种各样的沙雕视频。观众姥爷们(用户)看着这些沙雕视频很开心,有钱的赏钱,没钱的投个硬币,B站也算是圆满完成了自己的目标,二次元圈其乐融融。当然啦,一个菜虚鲲也是一个线程,它也在为着B站与观众老爷们的欢声笑语献出自己的一份力。
线程的几种状态
一个蔡徐坤被Up主创造(NEW
)出来后,点击上传视频按钮(线程的**start()**方法),它就处于一种可以跳鸡你太美的状态了(RUNNABLE
)。
现在只要等B站运维小姐姐(CPU调度系统)把它推荐到首页(CPU资源),观众姥爷就可以开看(pen)了。
同学们,我编不下去了。。。。我要开启奔放模式了。
正常来讲,在单线程的情况下,这只菜虚鲲就一直跳鸡你太美(RUNNING
),直到跳完(run()方法执行完毕),然后就处于谢幕状态(TERMINATED
)了,观众姥爷们也心满意足。
可现实是残酷的,首页坑位只有一个!其他沙雕视频里的菜虚鲲们(多个线程)也要跳鸡你太美!
这种情况下,第一个菜虚鲲(简称cxk001)就很难受。假如它正跳着呢,运维小姐姐突然把首页让给了了另一个视频里的菜虚鲲(cxk002)。cxk001发现,首页被占了,那就只能在角落里自己玩自己(回到RUNNABLE
状态),直到它重回主页。
除了首页,还有一个东西很重要。众所周知,菜虚鲲喜欢篮球,鸡你太美里面最精彩的也莫过于它那精湛的篮球技巧。而篮球(临界资源,排他锁),只有一个。
所以在这段篮球炫技片段中,没有篮球的话,菜虚鲲肯定不干的。所以,即便把首页给菜虚鲲,但没有篮球的话,菜虚鲲会处于一种非常沮丧的状态(blocked状态
),只有当其他的鲲把篮球让出来给这只鲲,它才能重拾信心,回到RUNNABLE
状态,为观众姥爷呈现一段完美的 鸡你太美!。
有些菜虚鲲很有奉献(摸鱼)精神。他说:我先摸会鱼,你们先跳。然后主动把首页和篮球(不都会)让出来一段时间(调用Thread.sleep(long timeout)、 Object.wait(long timeout)。这两者的区别在于,前者是对线程调用,后者对锁调用。前者不会释放锁,后者会) 。还有些鲲很给领导面子,他说:领导先跳,领导跳完了我再跳(调用leaderThread.join(long)), leaderThread是某一个优先级比较高的线程,该方法同样也会释放锁)。这些主动让出首页的菜虚鲲就处于一种 超时等待状态(TIMED_WAITING
)。当摸鱼时间结束后,它就又处于可以跳鸡你太美的状态了(RUNNABLE
)。
还有一种情况,那就是多管闲事菜虚鲲,动不动给其他鲲发律师函, 让那些没拿到篮球的菜虚鲲起来跳鸡你太美。有的只随机给一个鲲发(调用锁的notify()方法),有的比较绝,给所有人都发(调用锁的notifyAll()方法),被发函的鲲就得回到RUNNABLE
状态,心里美滋滋。
有的鲲心累啊,想跳不给跳,首页又不给,心态爆炸,直接说,老子不要球了,不跳了(调用锁对象的wait()方法,注意该方法没有指定时间长短)。此时,这只鲲就进入到无限期等待状态(WAITING
)状态,这时候就只能等那些多管闲事的鲲来给自己发律师函,才能回到RUNNABLE
状态。
现在,一只菜虚鲲的生命历程我们基本已经了解大半了。但是我们漏了一个重要情报!!!
怎么让篮球呢,当然是靠那些喜欢发律师函的鲲了(具体来讲,是通过调用篮球的notify()和notifyAll()方法)。
至此,我们也就介绍清楚了线程的整个生命周期。
下面开始一个菜虚鲲的一生总结:
- 被Up主创建出来(
NEW
) - Up主点击上传按钮,菜虚鲲准备好了,但还没有获得篮球或者首页坑位(
RUNNABLE
) - 获得篮球以及首页坑位,表演 鸡你太美!(
RUNNING
) - 失去篮球,对人生失去信心(
BLOCKED
) - 重新获得篮球,重拾信心(
RUNNABLE
) - 表演完毕,完美谢幕(
TERMINATED
) - 角落里摸鱼,好不自在(
TIMED_WAITING,WAITING
)
下面开始一个菜虚鲲的关键行为总结:
- 进行摸鱼(
Object.wait (), Thread.sleep()
)。这两个方法都会放弃首页(cpu资源),前者会放弃锁,后者不会。 - 给 一条/所有 菜虚鲲发律师函(
lockObject.notify(), lockObject.notifyAll()
)。该行为本质是唤醒那些需要锁资源的线程,lockObject也就是当前线程所持有的锁对象。 - 把首页让给其他领导菜虚鲲(
leaderThread.join()
,leaderThread
是程序设计中的其他高优先级线程),让出首页和篮球的鲲处于阻塞状态。需要注意的点是,如果鲲1让鲲2 join,但是此时鲲2还没有被创建的话,鲲1还是要继续跳下去的。
举个栗子
package org.oe.notify;
import java.util.ArrayList;
import java.util.List;
/**
* @program: learning-multi-thread
* @description: 菜虚鲲教你多线程
* @author: oe
* @create: 2019-04-29 14:15
*/
public class XuKunCai implements Runnable{
private Basketball basketball;
public static final String PERCENTAGE_PLACEHOLDER = "%";
public static final String RUNNING_REMARK = "正在表演 鸡你太美,当前进度:";
public static final String WAIT_REMARK = "让出了球权,下面有请他的弟弟们上台表演:";
public static final String NOTIFIED_REMARK = "弟弟被唤醒了,接下来看我的:";
private int progress;
public XuKunCai(Basketball basketball, int progress) {
this.basketball = basketball;
this.progress = progress;
}
public void run() {
dancingBeautifulChicken(basketball, 0);
}
public void dancingBeautifulChicken(Basketball basketball, int schedule) {
synchronized (basketball) {
while ((schedule = schedule + 50) <= 100) {
try {
basketball.notify();
System.out.println(Thread.currentThread().getName() + RUNNING_REMARK + schedule + PERCENTAGE_PLACEHOLDER);
System.out.println(Thread.currentThread().getName() + WAIT_REMARK);
basketball.wait(1000);
System.out.println(Thread.currentThread().getName() + NOTIFIED_REMARK);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "表演完毕");
basketball.notify();
}
}
public static void main(String[] args) {
Basketball basketball = new Basketball();
List<Thread> threadList = new ArrayList<>();
for (int i = 0; i< 3; i++) {
threadList.add(new Thread(new XuKunCai(basketball, 0), "菜虚鲲00"+(i + 1)));
}
threadList.get(0).setPriority(10);
threadList.get(1).setPriority(1);
threadList.get(2).setPriority(1);
threadList.forEach(thread -> thread.start());
boolean finish = false;
while (!finish) {
for (Thread thread : threadList) {
if (thread.getState() == Thread.State.BLOCKED) {
System.out.println(Thread.currentThread().getName() + ":我是"+ thread.getName() +
",优先级是"+thread.getPriority()+",我要报警了" +"又没拿到球");
try {
thread.join();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
if (threadList.get(0).getState() == Thread.State.TERMINATED &&
threadList.get(1).getState() == Thread.State.TERMINATED &&
threadList.get(0).getState() == Thread.State.TERMINATED ) {
finish = true;
}
}
}
private static class Basketball {
}
}
在这一实例中,我创建了三个菜虚鲲实例(这里采用的是实现Runnable接口),它们在Run()方法中执行的任务是dancingBeautifulChicken()
方法,该方法将打印该鲲的跳舞进度,每跳50%,就让出临界资源:basketball
对象,并唤醒其他阻塞的线程。最终,三只鲲都跳完鸡你太美,返回main()主线程。值得注意的是,由于dancingBeautifulChicken()
方法中加锁的对象是basketball
,所以释放锁和唤醒其他线程的操作也都是对basketball
调用相应方法完成的。
执行结果如下,这里我有个疑问,我对三个线程设置的优先级没有起到作用,有知道原因的小伙伴请帮我解答一下。
菜虚鲲001正在表演 鸡你太美,当前进度:50%
main:我是菜虚鲲002,优先级是1,我要报警了又没拿到球
菜虚鲲001让出了球权,下面有请他的弟弟们上台表演:
菜虚鲲003正在表演 鸡你太美,当前进度:50%
菜虚鲲003让出了球权,下面有请他的弟弟们上台表演:
菜虚鲲002正在表演 鸡你太美,当前进度:50%
菜虚鲲002让出了球权,下面有请他的弟弟们上台表演:
菜虚鲲003弟弟被唤醒了,接下来看我的:
菜虚鲲003正在表演 鸡你太美,当前进度:100%
菜虚鲲003让出了球权,下面有请他的弟弟们上台表演:
菜虚鲲002弟弟被唤醒了,接下来看我的:
菜虚鲲002正在表演 鸡你太美,当前进度:100%
菜虚鲲002让出了球权,下面有请他的弟弟们上台表演:
菜虚鲲003弟弟被唤醒了,接下来看我的:
菜虚鲲003表演完毕
菜虚鲲002弟弟被唤醒了,接下来看我的:
菜虚鲲002表演完毕
菜虚鲲001弟弟被唤醒了,接下来看我的:
菜虚鲲001正在表演 鸡你太美,当前进度:100%
菜虚鲲001让出了球权,下面有请他的弟弟们上台表演:
菜虚鲲001弟弟被唤醒了,接下来看我的:
菜虚鲲001表演完毕
Process finished with exit code 0
总结
本篇文章逻辑很不严谨,不太像博客,甚至有点像吐槽,大家看看笑一笑也就行了。目的在于给学习java多线程知识的你提供一种思路,欢迎评论区讨论。
声明:本文中出现的人物与事件均与现实世界无关,如有雷同,纯属偶合,拒绝接受任何律师函。
本文作者保有对该文章的最终解释权。
最后,转载本文请标明作者和出处。