Java 线程知识笔记 (十二) 线程之间数据交互与顺序实例

15 篇文章 1 订阅
9 篇文章 0 订阅

前言

考虑到之前写的博客太过于偏重理论性,这篇博客是Java线程的一个小的应用实例。我们的例子中会应用到线程中的wait,notifyAll,synchronized,volatile等等线程方法和关键字,来实现这个功能,也可以更好地让大家体会到这些线程相关的理论内容,在实际运行中有什么样子的表现。最后也会用Semaphore去从另一个思路实现相同的功能,用来提供另一种思路。更多线程知识内容请点击【Java 多线程和锁知识笔记系列】

场景

场景大意是:现在有三个线程,线程t1输出A,线程t2输出B,线程t3输出C。这个程序的目的:就是让这三个线程依次顺序输出其打印的内容10遍,最终交替顺序执行输出ABC这样一个任务

普通例子

首先分析我们要怎么做:首先三个线程,交互或者交替执行其实并不难。如果要顺序执行就必须让三个线程感知到对方是否正在执行自己的内容,那么根据我们之前所说的内容很容易想到我们可以用volatile关键字去做一个感知。为了区分三个线程的执行,那么我们也给三个线程各自一个名字。区分线程可以用if条件语句或者switch语句加上volatile修饰的信号量去做,大概分析结果结束,代码例子如下:

public class OrderOutput {
//	设置线程名,用来在交互过程中区分线程
	private static final String THREAD1="t1";
	private static final String THREAD2="t2";
	private static final String THREAD3="t3";
//	设置线程信号量,用来控制哪个线程需要被唤醒或等待
	private static volatile int flag=1;
//	设置执行次数信号量,用来控制线程执行次数
	private static volatile int stop=0;
	
	public static void main(String[] args) {
		InnerThread t=new InnerThread();
		new Thread(t,THREAD1).start();
		new Thread(t,THREAD2).start();
		new Thread(t,THREAD3).start();
	}
	private static class InnerThread implements Runnable{
		@Override
		public void run() {
			while (stop<30) {//控制线程执行次数、
				//这里使用synchronized锁住当前对象,其实锁住的就是上面main方法中的t,
				// 可以回忆下这里锁住的t,为什么能够锁住线程
				synchronized (this) { 
					stop++; //volatile变量标记,由于volatile并不能保证原子性,因此我们在synchronized里面操作
					switch (Thread.currentThread().getName()) {
					case THREAD1:
						while (flag!=1) { //当线程执行到这里发现信号量是t2或者t3时
							try {
								wait(0); //就把t1 wait住,等待t2或者t3先执行
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						}
						break;
					case THREAD2:
						while (flag!=2) { //同理
							try {
								wait(0);
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						}
						break;
					case THREAD3:
						while (flag!=3) { //同理
							try {
								wait(0);
							} catch (InterruptedException e) {
								e.printStackTrace();
							}
						}
						break;
					}
					try {
						Thread.sleep(100); //设置一个间隔时间,方便观察结果
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					//执行输出
					switch (Thread.currentThread().getName()) {
					case THREAD1:
						if (flag==1) { //当发现是线程t1,执行输出A
							System.out.print("A");
							flag=2; //并且修改线程标记为了唤醒t2
						}
						break;
					case THREAD2:
						if (flag==2) { //当发现是线程t2,执行输出B
							System.out.print("B");
							flag=3; //并且修改线程标记为了唤醒t3
						}
						break;
					case THREAD3:
						if (flag==3) { //当发现是线程t3,执行输出C
							System.out.print("C");
							flag=1; //并且修改线程标记为了唤醒t1
						}
						break;
					}
					//唤醒所有线程,开始竞争资源
					notifyAll();
				}
			}
		}
	}
}
-------------输出-------------------
ABCABCABCABCABCABCABCABCABCABC

上面程序的执行结果就是ABC这样交替顺序输出,由于用的print()不是println()所以就有了这样一个效果。这个例子其实非常的简单明了,只有一点要再次说下:怎么控制第一个是谁输出的呢?就是我们初始化信号量flag,这里设置的。笔者默认的是1就会使得t1先去执行,同样的,当线程被wait住以后,调用notifyAll()方法时,也只有flag信号量的值对应的线程能够执行。

Semaphore 实现

说完普通的例子,咱们在看一个Semaphore的实现,同样我们需要一个信号量来控制输出,以及一个信号量来控制执行次数。由于Semaphore给我们提供了获取锁与释放锁的方法,因此我们就不需要在使用线程名字控制线程,直接使用Semaphore进行线程顺序控制即可,原理都是差不多的,只不过提供了另一个思路,例子如下:

public class OrderOutputSemaphore {
	private Semaphore s1=new Semaphore(1);
	private Semaphore s2=new Semaphore(1);
	private Semaphore s3=new Semaphore(1);
//	设置线程信号量,用来控制哪个线程需要被唤醒或等待
	private static volatile int flag=1;
//	设置执行次数信号量,用来控制线程执行次数
	private static volatile int stop=0;
//	创建第一个线程
	private Thread t1=new Thread(()->{
		while (stop<30) {
			stop++; //这里为了保证原子性,最好锁一下
			try {
				if (flag!=1) { //信号量flag不是1,加锁
					s1.acquire();
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			if (flag==1) { //信号量flag是1,执行逻辑
				System.out.print("A");
				flag=2; //修改信号量为2
				s2.release(); //释放s2,让程序继续执行
			}
		}
	});
	
	private Thread t2=new Thread(()->{
		while (stop<30) {
			stop++;
			try {
				if (flag!=2) {
					s2.acquire();
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			if (flag==2) {
				System.out.print("B");
				flag=3;
				s3.release();
			}
		}
	});
	
	private Thread t3=new Thread(()->{
		while (stop<30) {
			stop++;
			try {
				if (flag!=3) {
					s3.acquire();
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
			try {
				Thread.sleep(100);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			if (flag==3) {
				System.out.print("C");
				flag=1;
				s1.release();
			}
		}
	});
	
	public static void main(String[] args) {
		OrderOutputSemaphore order=new OrderOutputSemaphore();
		order.t1.start();
		order.t2.start();
		order.t3.start();
	}
}
-------------输出-------------------
ABCABCABCABCABCABCABCABCABCABC
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java面试资源概览 一、内容概览 本次分享的资源涵盖了Java面试的各个方面,从基础知识到高级技术,从数据库到框架应用,都做了深入的探讨和总结。具体内容包括: Java基础知识点:包括数据类型、面向对象特性、异常处理、集合框架等。 Java核心技术:如多线程、网络编程、序列化等都有详细的解释和示例。 常用框架:如Spring、MyBatis等框架的使用方法和内部原理都有涉及。 数据库相关:包括关系型数据库和非关系型数据库的使用,以及JDBC、MyBatis等与数据交互的技术。 实战项目经验:分享了几个经典的Java项目,解析了项目的架构设计和核心技术点。 面试经验和技巧:整理了常见的Java面试问题,并给出了答题建议和技巧。 代码和项目实例:提供了多个Java项目的源代码,方便学习者参考和实践。 学习笔记和心得:记录了学习过程中的重点难点和心得体会,有助于学习者更好地理解和掌握知识。 二、适用人群 本资源适用于即将毕业或已经毕业,希望通过学习Java找到一份理想工作的同学。无论你是初学者还是有一定基础的开发者,都能从中获得启发和帮助。 三、使用建议 系统学习:建议学习者按照资源提供的顺序和内容,系统地学习和掌握Java知识点。 实践为王:理论知识和实战经验相结合,通过实践来加深理解和记忆。 持续更新:由于Java技术和面试要求都在不断更新,建议学习者保持关注,随时更新自己的知识和技能。 交流与讨论:与同学或同行进行交流和讨论,分享学习心得和经验,共同进步。Java面试资源概览 一、内容概览 本次分享的资源涵盖了Java面试的各个方面,从基础知识到高级技术,从数据库到框架应用,都做了深入的探讨和总结。具体内容包括: Java基础知识点:包括数据类型、面向对象特性、异常处理、集合框架等。 Java核心技术:如多线程、网络编程、序列化等都有详细的解释和示例。 常用框架:如Spring、MyBatis等框架的使用方法和内部原理都有涉及。 数据库相关:包括关系型数据库和非关系型数据库的使用,以及JDBC、MyBatis等与数据交互的技术。 实战项目经验:分享了几个经典的Java项目,解析了项目的架构设计和核心技术点。 面试经验和技巧:整理了常见的Java面试问题,并给出了答题建议和技巧。 代码和项目实例:提供了多个Java项目的源代码,方便学习者参考和实践。 学习笔记和心得:记录了学习过程中的重点难点和心得体会,有助于学习者更好地理解和掌握知识。 二、适用人群 本资源适用于即将毕业或已经毕业,希望通过学习Java找到一份理想工作的同学。无论你是初学者还是有一定基础的开发者,都能从中获得启发和帮助。 三、使用建议 系统学习:建议学习者按照资源提供的顺序和内容,系统地学习和掌握Java知识点。 实践为王:理论知识和实战经验相结合,通过实践来加深理解和记忆。 持续更新:由于Java技术和面试要求都在不断更新,建议学习者保持关注,随时更新自己的知识和技能。 交流与讨论:与同学或同行进行交流和讨论,分享学习心得和经验,共同进步。Java面试资源概览 一、内容概览 本次分享的资源涵盖了Java面试的各个方面,从基础知识到高级技术,从数据库到框架应用,都做了深入的探讨和总结。具体内容包括: Java基础知识点:包括数据类型、面向对象特性、异常处理、集合框架等。 Java核心技术:如多线程、网络编程、序列化等都有详细的解释和示例。 常用框架:如Spring、MyBatis等框架的使用方法和内部原理都有涉及。 数据库相关:包括关系型数据库和非关系型数据库的使用,以及JDBC、MyBatis等与数据交互的技术。 实战项目经验:分享了几个经典的Java项目,解析了项目的架构设计和核心技术点。 面试经验和技巧:整理了常见的Java面试问题,并给出了答题建议和技巧。 代码和项目实例:提供了多个Java项目的源代码,方便学习者参考和实践。 学习笔记和心得:记录了学习过程中的重点难点和心得体会,有助于学习者更好地理解和掌握知识。 二、适用人群 本资源适用于即将毕业或已经毕业,希望通过学习Java找到一份理想工作的同学。无论你是初学者还是有一定基础的开发者,都能从中获得启发和帮助。 三、使用建议 系统学习:建议学习者按照资源提供的顺序和内容,系统地学习和掌握Java知识点。 实践为王:理论知识和实战经验相结合,通过实践来加深理解和记忆。 持续更新:由于Java技术和面试要求都在不断更新,建议学习者保持关注,随时更新自己的知识和技能。 交流与讨论:与同学或同行进行交流和讨论,分享学习心得和经验,共同进步。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值