我和Java Thread的故事

目录

与设想结果出入1

猜想

验证

结论

与设想结果出入2

 分析

主线程等子线程执行完再执行

Thread.join方法

CountDownLatch

CyclicBarrier

参考资料


先看一段简单的代码:

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种常见的方法:

  1. Thread.join方法;
  2. CountDownLatch;
  3. 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. 初始化,给定计数大小;
  2. 完成任务后,计数器减一;
  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步:

  1. 初始化,给定屏障大小(实现同样效果,大小比CountDowanLatch大1);
  2. 完成任务后,通知到达屏障;
  3. 所有任务到达屏障,进行下一步;

第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详细的区别,这里就不细说了。

参考资料

Java Thread的join() 原理

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值