目录
先看一段简单的代码:
public static void main(String[] args) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 1; i < 6; i++) {
System.out.println(i);
}
}
});
thread.start();
System.out.println("Main thread end.");
}
这段代码的运行结果会是什么呢?结果如下图:
我运行了10次,仍然是上图结果。
与设想结果出入1
按照之前的设想,thread与主线程是并行或者并发执行,打印结果中,“Main thread end.”应该出现在1,2,3,4,5中的任何位置,为什么执行10次,都是一样的结果呢?
猜想
现在线程一般是按照时间片调度的,以上的测试代码中,代码量很少,不管是主线程、还是thread线程,都足以在一个时间片内处理完成。所以,结果最终都是一样的。
验证
所以,我们改一改代码,让主线程执行时间长一点。
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 1; i < 6; i++) {
System.out.println(i);
}
}
});
thread.start();
Thread.sleep(1);
System.out.println("Main thread end.");
}
运行结果“Main thread end.”打印位置出现了随机性,如下图:
结论
猜想正确,主线程和thread线程执行时间太短,2个线程都在一个时间片内执行完成,所以执行结果总是一样。
与设想结果出入2
执行之前,还设想过另一种结果:主线程结束,子线程随之结束,子线程结束时,可能是打印到1,2,3,4,5的任意一个。设想结果如下图:
分析
经过分析,与守护线程有关。
守护线程:一种服务其他线程的线程。特点是Java虚拟机中,如果只剩下守护线程,则虚拟机就会退出。因为守护线程是为其他线程服务的,如果服务的线程都结束了,守护线程存在也就没有意义了,虚拟机也就没有必要再继续运行了。比如虚拟机的GC线程,就是一种守护线程,如果其他线程都结束了,也就不会再产生垃圾了,GC线程也就没有存在的意义了,虚拟机也就可以退出了。
查看Thread源码可知,新建线程时,线程的守护属性是随父线程的,如下:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
/** 省略部分代码 */this.group = g;
this.daemon = parent.isDaemon();
this.priority = parent.getPriority();
/** 省略部分代码 */
}
父线程,即主线程,自然不是守护线程。所以thread也不是守护线程。
为了实现猜想中的结果,把thread线程设置为守护线程,再加一些延时代码即可,代码如下:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 1; i < 6; i++) {
System.out.println(i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.setDaemon(true);
thread.start();
System.out.println("Main thread end.");
Thread.sleep(new Random().nextInt(500));
}
主线程等子线程执行完再执行
如果不加任何处理,主线程和子线程是并发或者并行处理的,并没有强制的先后顺序。要实现主线程等子线程执行完再执行,有3种常见的方法:
- Thread.join方法;
- CountDownLatch;
- CyclicBarrier。
Thread.join方法
join方法可以让其他线程,等待调用join的线程完成再执行。比如在主线程中调用thread.join,则主线程会等待thread执行完成后再往下执行。
join方法的本质是把线程当成监视器,通过监视器的wait方法,notifyAll方法实现等待和唤醒。比如把thread线程当成监视器,在主线程中调用thread.join方法,相当于调用thread.wait方法,thread运行结束后,会调用thread.notifyAll方法。深入分析可以参考资料:Java Thread的join() 原理。
代码如下:
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 1; i < 6; i++) {
System.out.println(i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
thread.start();
thread.join();
System.out.println("Main thread end.");
}
运行结果如下:
CountDownLatch
CountDownLatch相当于一个计数器,使用有3步:
- 初始化,给定计数大小;
- 完成任务后,计数器减一;
- 在需要的地方等待;
第1步代码代码如下:
CountDownLatch latch = new CountDownLatch(1);
第2步代码如下:
latch.countDown();
第3步代码如下(注意是await方法,不是wait方法):
latch.await();
完整代码如下:
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 1; i < 6; i++) {
System.out.println(i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
latch.countDown();
}
});
thread.start();
latch.await();
System.out.println("Main thread end.");
}
CyclicBarrier
CyclicBarrier称为屏障,和CountDowanLatch用法很相似,也有3步:
- 初始化,给定屏障大小(实现同样效果,大小比CountDowanLatch大1);
- 完成任务后,通知到达屏障;
- 所有任务到达屏障,进行下一步;
第1步代码如下:
CyclicBarrier barrier = new CyclicBarrier(2);
第2步代码:
barrier.await();
第3步代码:
barrier.await();
完整代码如下:
public static void main(String[] args) throws Exception {
CyclicBarrier barrier = new CyclicBarrier(2);
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 1; i < 6; i++) {
System.out.println(i);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
barrier.await();
} catch (Exception e) {
e.printStackTrace();
}
}
});
thread.start();
barrier.await();
System.out.println("Main thread end.");
}
当然CyclicBarrier和CountDownLatch详细的区别,这里就不细说了。
参考资料