前言
大家好,一直以来我都本着用最通俗的话理解核心的知识点, 我认为所有的难点都离不开 基础知识 的铺垫。目前正在出一个 Java多线程专题
长期系列教程,从入门到进阶, 篇幅会较多, 喜欢的话,给个关注:heart: ~
适合人群
- 有一定的Java基础
- 想学习或了解多线程开发
- 想提高自己的同学
大佬可以绕过 ~
背景
之前给大家讲了一些 框架
的使用,这些都属于业务层面的东西,你需要熟练掌握它并在项目中会运用它即可,但这些对自身技术的积累是远远不够的,如果你想要提高自己,对于语言本身你需要花更多的时间去挖掘而不是局限于框架的使用,所以之前为什么跟大家一直强调 基础
的重要性,框架可以千变万化,层出不穷,但是基础它是不变的,不管是学 java
还是 前端
或者是其它语言, 这一点大家还是需要认清的。
接下来的几期会专门讲 多线程
这一块,篇幅会较多,耐心看完你一定会有 收获
~
情景回顾
上期带大家学习了什么是入门学习了 Thread类和Runable接口
,本期带大家进阶学习 Thread
的用法,我们一起来看一下吧~
Thread类的常用方法
在了解之前,我们先看下它的初始化方法
构造函数
- 无参数
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
复制代码
- 有参数
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
Thread(Runnable target, AccessControlContext acc) {
init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}
....
复制代码
其实我们从源码可以发现,构造函数内部,都调用了初始化方法 init
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
...
}
复制代码
其实要给大家讲的是它这几个参数的意思,可以了解一下
-
g
线程组,指定这个线程是在哪个线程组下 -
target
target:指定要执行的任务 -
name
线程的名字,多个线程的名字是可以重复的 -
acc
用于初始化私有变量inheritedAccessControlContext
-
inheritThreadLocals
可继承的ThreadLocal
这个大家了解下就行, 下面我们看看常用的方法
currentThread
currentThread()
:native静态方法,返回对当前正在执行的线程对象的引用;
public static native Thread currentThread();
复制代码
public static void main(String[] args) {
Thread t = new Thread(() -> {
System.out.println("hello");
});
System.out.println(t.currentThread());
t.start();
System.out.println(t.currentThread());
}
复制代码
输出
Thread[main,5,main]
Thread[main,5,main]
hello
复制代码
start
start()
:开始执行线程的方法,java虚拟机会调用线程内的run()方法;
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
复制代码
同时会加入线程组,这里执行的 start0();
是一个 native
方法
yield
yield()
指的是当前线程愿意让出对当前处理器的占用,这个怎么理解呢,看一个例子
Thread t = new Thread(() -> {
Thread.yield();
System.out.println("hello1");
});
Thread t1 = new Thread(() -> {
System.out.println("hello2");
});
Thread t2= new Thread(() -> {
System.out.println("hello3");
});
t.start();
t1.start();
t2.start();
复制代码
实际输出:
hello2
hello3
hello1
复制代码
可以看到 t
调用了 yield
方法,最后输出,我们大体可以猜到,线程 t
让步,线程 t1
先抢到cpu实际开始执行所以, hello2
先输出
sleep
sleep()
:native静态方法,使当前线程睡眠一段时间,单位是 ms, 类似js的setTimeout
,但sleep非异步,会阻塞当前线程
public static native void sleep(long millis) throws InterruptedException;
复制代码
其实还有一个非native的方法,但是内部调的还是native, 可以用于指定时间范围内的休眠
public static void sleep(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
sleep(millis);
}
复制代码
在很久之前的网站,有些老板会使用骚操作,开会员少休眠几秒,不开会员响应给你慢点
join
- join():使当前线程等待另一个线程执行完毕之后再继续执行,内部调用的是Object类的
wait
方法实现的;这个需要大家好好理解
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(() -> {
System.out.println("hello1");
});
t.join();
Thread t1 = new Thread(() -> {
System.out.println("hello2");
});
Thread t2= new Thread(() -> {
System.out.println("hello3");
});
t.start();
t1.start();
t2.start();
}
复制代码
这里的join是一个实例方法,我们看下时间输出
hello2
hello1
hello3
复制代码
发现t线程在t1线程后执行, 跟我们之前讲的 yield
还不太一样, 虽然也有让步的意思,但是让完就执行了,就是说,等待另一个线程也就是t1执行完再执行, 而 yield
是让出cpu时间片, 那我t2也让一下,是不是t3就先执行了,是这样吗:question:我们来看一下
Thread t = new Thread(() -> {
System.out.println("hello1");
});
t.join();
Thread t1 = new Thread(() -> {
System.out.println("hello2");
});
t1.join();
Thread t2= new Thread(() -> {
System.out.println("hello3");
});
t.start();
t1.start();
t2.start();
复制代码
实际输出
hello2
hello1
hello3
复制代码
发现结果还是没变化,这是怎么回事呢?t1join之后,应该是t3先执行才对,想知道原因还得看它的实现,我们看下源码
public final void join() throws InterruptedException {
join(0);
}
复制代码
发现内部是调了一个 join
方法
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
复制代码
这个方法加了一个 synchronized 同步锁
, synchronized在同一时刻只允许一个线程执行,如果有其它线程执行,需要等待当前线程执行完毕后再执行,具体的用法,后边给大家讲 锁
的时候讲。
这个方法可以传入指定时间,就是说等待n毫秒让该线程终止,如果为 millis为0
,会先判断线程是否处于存活状态,存在就wait(0)等待0毫秒。如果 millis大于0
,同样会先判断线程是否处于存活状态,并延迟deplay,当 delay小于等于0
跳出循环,其实循环到这,也相当于等待 millis
时间了,因为这个过程需要一直判断线程的状态
isAlive()
方法是一个native方法
public final native boolean isAlive();
复制代码
如果线程已启动且尚未死亡,则该线程处于活动状态。如果该线程还活着,则为true ;否则false 。
wait
也是一个native方法:
public final native void wait(long timeout) throws InterruptedException;
复制代码
使当前线程等待,直到另一个线程为此对象调用notify()方法或notifyAll()方法,或者经过了指定的时间量。 timeout – 等待的最长时间(以毫秒为单位)。这里其实涉及到 线程状态切换
的知识了,这个我们放到后边讲
结束语
本期到这里就结束了, 总结一下,本节主要讲了 Thread类
的常用方法,建议大家可以自己多试试~