多线程
多线程生命周期
六种线程状态
new:这里可以通过new Thread创建一个新的线程
runnable:这里是线程的运行区,他其实jvm里面不区分Ready和running区域的,因为这两者之间切换实在太快,就放在一个区域里面了。
terminated:当线程运行结束,就会在这个区域被终止。
timed_wating:用户调用Thread.sleep的方法的时候会进入这个模式。
wating:与timed_wating不同的点在于,他是没有计时的等待状态,并且两者都会把锁给释放掉。线程如果调用Object.wait或者Thread.join方法也会释放掉锁。
blocked:阻塞状态,当用户进入runnable以后,此时没有多余的锁给线程调用,线程就会进入阻塞状态,这个状态相当于是一个阻塞队列,线程在这里排队获取锁进入到runnable中去。
join和wait方法为什么前者属于Thread中,而后者属于Obejct中?
先说结论:join是控制线程,wait是控制对象上的锁。
如果我们使用Obejct类,我们创建的就是一个针对对象的操作,比如wait吧,我们先理解一个问题,锁是在哪里的?
锁是存在于我们要获取到的对象身上的,假如我们的线程A获取到了对象身上的锁,我们直接使用对象.wait()。
下面这个情况就是线程A获取锁以后,主线程在没有获取锁的情况下进行notify:
@Log4j
public class WaitTest {
public static void main(String[] args) {
Object lock = new Object();
Thread threadA = new Thread(() -> {
synchronized (lock) {
log.info("获取了锁");
try {
log.info("休眠一会儿");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("调用wait..");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("被唤醒");
}
}, "A");
threadA.start();
lock.notify();
}
}
这样会报错
那我们要怎么调用这个notify呢?
我们要通过线程B先去获取这个锁,然后调用notify,就可以唤醒A。
@Log4j
public class WaitTest {
public static void main(String[] args) {
Object lock = new Object();
Thread threadA = new Thread(() -> {
synchronized (lock) {
log.info("获取了锁");
try {
log.info("休眠一会儿");
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("调用wait..");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("被唤醒");
}
}, "A");
threadA.start();
Thread threadB = new Thread(()->{
synchronized (lock) {
log.info("获得了锁");
log.info("叫醒A");
lock.notify();
}
}, "B");
threadB.start();
}
}
上面这个例子,就很形象的举例出来了,使用Object获取锁释放锁。
那么join呢?他又是干什么的?
Java多线程中,wait()和join()的区别,带你入门不放弃 - 简书
这篇文章深刻的解析了wait和join的区别,其中有句话,是我们一定要掌握的。
wait就是让当前线程释放锁,join就是让当前释放锁,并且将这把锁交给join的线程,等他运行完毕再继续自己的线程。
public class Main{
public static void main(String[] args) {
System.out.println("Main 线程 开始运行!");
Thread t1 = new Thread(){
@Override
public void run(){
System.out.println("t1 开始运行!");
System.out.println("t1 结束运行!");
}
};
try{
t1.start();
t1.join();
}catch(Exception e){
}
System.out.println("Main 线程 结束运行!");
}
}
/*打印结果为:
Main 线程 开始运行!
t1 开始运行!
t1 结束运行!
Main 线程 结束运行!*/
类似于这样。
join的底层
那么现在有个问题,我们可以先join再start嘛?
我们来看看底层源码:
public final void join(long millis) throws InterruptedException {
synchronized(lock) {//主线程拿到lock锁
long base = System.currentTimeMillis();
if (millis == 0) {
while (isAlive()) { //由于该线程已经start(),所以视为alive
lock.wait(0);
//主线程释放锁,进入无限期的等待状态。
//直到子线程完成run,释放锁,然后主线程会重新拿到锁头继续运行
//拿到锁之后,isAlive()不成立了,所以退出while循环!!
}
} else {
}
}
}
}
先说结论,不可以!
为什么不可以?我们调用start()方法的时候,他这个isAlive变量会变成true代表线程存活,如果不调用,那么就不会变成true,join方法整个无法生效。
join的底层就是wait只有线程存活的时候,wait才能生效。
那么读者看到这里可以猜一下,我们刚刚那个例子join和start互换以后,输出的结果是什么呢?
显然join无法生效,线程start以后,主线程会继续执行,结果就是:
Main 线程 开始运行!
Main 线程 结束运行!
t1 开始运行!
t1 结束运行!
有意思吧!这就是底层源码的魅力。
join为什么可以让主线程休息?
因为我们前面说过了,join的本质就是wait,wait干嘛啦?让当前运行线程释放锁了啊!
同样看上面代码:
-
主线程进入join()方法
-
主线程拿到子线程的lock锁
-
进入同步代码快
-
while (isAlive()) 成立,因为先调用了start()方法
-
调用 lock.wait(0), 主线程释放锁,进入wait状态
-
子线程开始执行,执行结束会调用lock.notifyAll(),通知主线程获得锁。
-
主线程重新启动, while (isAlive()) 已经不成立(由于子线程不再是alive状态)
-
主线程继续往下运行。