JAVA线程分组顺序执行

             通过初步学习多线程后,知道如果新启动一个线程,新线程会独立执行任务,当前线程会继续往下执行。有时我们有这样的要求,必须等新线程执行完后,当前线程才能继续往下执行。满足这样的需求,JAVA中有多种方法。

1.join()方法

        在线程中,可以使用join()方法让一个线程强制运行,线程强制运行期间其它线程无法运行,必须等待此线程完成后才可以继续执行。

package cn.thread;

public class ThreadJoinTest {
	
	private final static int GROUP_SIZE = 5;

	public static void main(String []args) throws InterruptedException {
		Thread [] threadGroup1 = new Thread[GROUP_SIZE];
		Thread [] threadGroup2 = new Thread[GROUP_SIZE];
		createThreadGroup(threadGroup1,"第1线程组");
		createThreadGroup(threadGroup2,"第2线程组");
		//启动第1线程组
		startThreadGroup(threadGroup1);
		//阻塞当前线程,等待第1线程组全部执行完毕
		joinThreadGroup(threadGroup1);
		System.out.println("第1线程组全部执行完毕!!!!!!!!");
		//启动第2线程组
		startThreadGroup(threadGroup2);
		//阻塞当前线程,等待第2线程组全部执行完毕
		joinThreadGroup(threadGroup2);
		System.out.println("第2线程组全部执行完毕!!!!!!!!");
	}
	
	private static void createThreadGroup(Thread[] group,final String name){
		for(int i=0;i<group.length;i++){
			final int num=i+1;
			group[i]=new Thread(){
				public void run(){
					for(int j=1;j<=5;j++){
						System.out.println("我是"+name+" "+num+"号"+"第"+j+"次运行.....");
					}
				}
			};
		}
	}
	private static void startThreadGroup(Thread[] group){
		for(Thread t:group){
			t.start();
		}
	}
	private static void joinThreadGroup(Thread[] group){
		for(Thread t:group){
			try {
				t.join();//强制让线程t运行
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}


运行结果

我是第1线程组 1号第1次运行.....
我是第1线程组 1号第2次运行.....
我是第1线程组 1号第3次运行.....
我是第1线程组 1号第4次运行.....
我是第1线程组 1号第5次运行.....
我是第1线程组 2号第1次运行.....
我是第1线程组 2号第2次运行.....
我是第1线程组 2号第3次运行.....
我是第1线程组 2号第4次运行.....
我是第1线程组 2号第5次运行.....
我是第1线程组 3号第1次运行.....
我是第1线程组 3号第2次运行.....
我是第1线程组 3号第3次运行.....
我是第1线程组 3号第4次运行.....
我是第1线程组 4号第1次运行.....
我是第1线程组 4号第2次运行.....
我是第1线程组 4号第3次运行.....
我是第1线程组 4号第4次运行.....
我是第1线程组 4号第5次运行.....
我是第1线程组 5号第1次运行.....
我是第1线程组 5号第2次运行.....
我是第1线程组 5号第3次运行.....
我是第1线程组 5号第4次运行.....
我是第1线程组 5号第5次运行.....
我是第1线程组 3号第5次运行.....
第1线程组全部执行完毕!!!!!!!!
我是第2线程组 1号第1次运行.....
我是第2线程组 1号第2次运行.....
我是第2线程组 1号第3次运行.....
我是第2线程组 3号第1次运行.....
我是第2线程组 3号第2次运行.....
我是第2线程组 3号第3次运行.....
我是第2线程组 3号第4次运行.....
我是第2线程组 3号第5次运行.....
我是第2线程组 2号第1次运行.....
我是第2线程组 2号第2次运行.....
我是第2线程组 2号第3次运行.....
我是第2线程组 2号第4次运行.....
我是第2线程组 2号第5次运行.....
我是第2线程组 4号第1次运行.....
我是第2线程组 4号第2次运行.....
我是第2线程组 4号第3次运行.....
我是第2线程组 1号第4次运行.....
我是第2线程组 4号第4次运行.....
我是第2线程组 1号第5次运行.....
我是第2线程组 5号第1次运行.....
我是第2线程组 5号第2次运行.....
我是第2线程组 5号第3次运行.....
我是第2线程组 5号第4次运行.....
我是第2线程组 5号第5次运行.....
我是第2线程组 4号第5次运行.....
第2线程组全部执行完毕!!!!!!!!

2.CountDownLatch

     CountDownLatch主要起倒计时计数器作用,它主要有两个方法await()和countDown()。一旦某个线程调用await()方法,那么该线程就会阻塞,等待CountDownLatch计数器倒计时归零,需要注意的是尽管线程调用await()方法后会阻塞,但是CountDownLatch允许别的线程调用countDown()方法,将计数器减一。也就是说调用计时器的线程阻塞后,可以利用别的线程控制调用线程何时从新开始运行。

   CountDownLatch 是一个通用同步工具,它有很多用途。将计数 1 初始化的CountDownLatch 用作一个简单的开/关锁存器,或入口:在通过调用 countDown() 的线程打开入口前,所有调用await 的线程都一直在入口处等待。用 N 初始化的 CountDownLatch 可以使一个线程在 N个线程完成某项操作之前一直等待,或者使其在某项操作完成 N 次之前一直等待。

     CountDownLatch 的一个有用特性是,它不要求调用countDown 方法的线程等到计数到达零时才继续,而在所有线程都能通过之前,它只是阻止任何线程继续通过一个 await

 

package cn.thread;

import java.util.concurrent.CountDownLatch;

/**
 * 模拟运动员跑步
 * 同组的运动必须都准备就绪后才能出发
 *
 */
public class CountDownLatchTest {
	
	private final static int GROUP_SIZE = 5;
	
	public static void main(String []args) {
		processOneGroup("分组1");
		processOneGroup("分组2");
	}
	
	private static void processOneGroup(final String groupName) {
		final CountDownLatch start_count_down = new CountDownLatch(1);//起到锁的作用
		final CountDownLatch end_count_down = new CountDownLatch(GROUP_SIZE);//模拟运动员计数器
		System.out.println( groupName + "比赛开始:");
		for(int i = 0 ; i < GROUP_SIZE ; i++) {
			new Thread(String.valueOf(i)) {
				public void run() {
					System.out.println("【" + groupName + "】,第" + this.getName() + " 号线程,我已经准备就绪!");
					try {
						start_count_down.await();//进行阻塞,等待其他运动员准备就绪
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("【" + groupName + "】,第" + this.getName() + " 号线程,我已执行完成!");
					end_count_down.countDown();//其中一个运动员完成,计算器-1
				}
			}.start();
		}
		try {
			Thread.sleep(1000);//让所有运动都准备好
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println(groupName+"预备!Go!!!");
		start_count_down.countDown();//解锁,运动员开始赛跑
		try {
			end_count_down.await();//阻塞父线程,等到所有运动员跑完后(end_count_down为0),再往下执行
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		System.out.println( groupName + "比赛结束!");
	}
}


运行结果

分组1比赛开始:
【分组1】,第0 号线程,我已经准备就绪!
【分组1】,第2 号线程,我已经准备就绪!
【分组1】,第3 号线程,我已经准备就绪!
【分组1】,第1 号线程,我已经准备就绪!
【分组1】,第4 号线程,我已经准备就绪!
分组1预备!Go!!!
【分组1】,第0 号线程,我已执行完成!
【分组1】,第1 号线程,我已执行完成!
【分组1】,第4 号线程,我已执行完成!
【分组1】,第3 号线程,我已执行完成!
【分组1】,第2 号线程,我已执行完成!
分组1比赛结束!
分组2比赛开始:
【分组2】,第1 号线程,我已经准备就绪!
【分组2】,第0 号线程,我已经准备就绪!
【分组2】,第3 号线程,我已经准备就绪!
【分组2】,第2 号线程,我已经准备就绪!
【分组2】,第4 号线程,我已经准备就绪!
分组2预备!Go!!!
【分组2】,第1 号线程,我已执行完成!
【分组2】,第0 号线程,我已执行完成!
【分组2】,第2 号线程,我已执行完成!
【分组2】,第3 号线程,我已执行完成!
【分组2】,第4 号线程,我已执行完成!
分组2比赛结束!

 
 
 
 
 
 
 
 
Java 中代码执行顺序问题可能由多种因素引起,包括多线程环境下的锁顺序、类加载机制、流处理顺序、以及 SQL 与内存处理顺序不一致等。以下从多个角度分析 Java 代码执行顺序问题的排查与解决方案,并结合实际场景提供具体建议。 ### 三、多线程执行顺序问题与死锁排查 在并发编程中,线程调度由 JVM 和操作系统共同决定,因此线程执行顺序具有不确定性。当多个线程访问共享资源时,若未合理控制同步顺序,可能导致死锁或执行顺序混乱。 例如,在 `OrderService` 类中,`createOrder()` 与 `cancelOrder()` 方法分别以不同顺序获取锁,最终导致死锁[^2]。此类问题的解决方式包括: - **统一锁获取顺序**:确保所有方法按照相同的顺序获取锁。 - **使用 `ReentrantLock` 并设置超时**:避免线程无限期等待,增强程序健壮性。 - **利用 `java.util.concurrent` 包中的高级并发工具**,如 `Semaphore`、`CountDownLatch` 等,控制线程执行顺序。 ```java // 使用 ReentrantLock 控制锁顺序 public class OrderService { private final ReentrantLock orderLock = new ReentrantLock(); private final ReentrantLock inventoryLock = new ReentrantLock(); public void createOrder() { orderLock.lock(); try { inventoryLock.lock(); // 扣减库存 } finally { inventoryLock.unlock(); orderLock.unlock(); } } public void cancelOrder() { orderLock.lock(); try { inventoryLock.lock(); // 还原库存 } finally { inventoryLock.unlock(); orderLock.unlock(); } } } ``` ### 三、类加载顺序引发的执行顺序问题 Java 类加载器在加载类时遵循双亲委派模型,但在某些情况下(如多个 jar 包中存在同名类),类加载顺序将直接影响最终加载的类版本。例如,使用 `java -cp` 指定多个 jar 包时,JVM 会按照 `-cp` 参数的顺序依次查找类[^4]。 为避免类加载顺序导致的执行异常: - **明确指定类路径顺序**,确保优先加载预期版本。 - **使用 `ClassLoader` 显式加载特定类**,避免默认加载机制导致的歧义。 - **启用类加载跟踪**:使用 `-XX:+TraceClassLoading` 查看类加载顺序。 ### 三、流处理与 SQL 查询顺序不一致问题 在实际开发中,SQL 查询结果顺序正确,但在 Java 中使用 `Collectors.groupingBy()` 等操作后,组内顺序可能“错乱”。这通常是因为使用了无序的集合(如 `HashMap`)作为分组容器。 解决方法包括: - **使用 `LinkedHashMap` 维护插入顺序**: ```java Map<String, List<User>> grouped = users.stream() .collect(Collectors.groupingBy(User::getDepartment, LinkedHashMap::new, Collectors.toList())); ``` - **在分组后对每组数据进行排序**: ```java Map<String, List<User>> grouped = users.stream() .collect(Collectors.groupingBy(User::getDepartment, Collectors.collectingAndThen(Collectors.toList(), list -> list.stream().sorted(Comparator.comparing(User::getName)).toList()))); ``` ### 三、JVM 执行顺序优化与指令重排 JVM 为了提高性能,可能对指令进行重排序(Instruction Reordering),这在多线程环境下可能导致执行顺序与代码顺序不一致。为防止此类问题: - **使用 `volatile` 保证变量可见性与禁止指令重排**。 - **使用 `synchronized` 或 `Lock` 保证代码块的原子性与可见性**。 - **使用 `java.util.concurrent.atomic` 包中的原子变量**。 ### 三、调试与排查工具 - **线程死锁排查**:使用 `jstack` 查看线程堆栈,识别死锁线程与资源等待关系。 - **类加载顺序分析**:通过 `-XX:+TraceClassLoading` 查看类加载顺序。 - **流处理顺序验证**:在分组、排序后打印中间结果,确认数据顺序是否符合预期。 ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值