Java并发技术05:传统线程同步通讯技术

欢迎关注个人微信公众号:程序员私房菜(id:eson_15)java

先看一个问题:程序员

有两个线程,子线程先执行10次,而后主线程执行5次,而后再切换到子线程执行10,再主线程执行5次……如此往返执行50次。安全

看完这个问题,很明显要用到线程间的通讯了, 先分析一下思路:首先确定要有两个线程,而后每一个线程中确定有个50次的循环,由于每一个线程都要往返执行任务50次,主线程的任务是执行5次,子线程的任务是执行10次。线程间通讯技术主要用到 wait() 方法和 notify() 方法。wait() 方法会致使当前线程等待,并释放所持有的锁,notify() 方法表示唤醒在此对象监视器上等待的单个线程。下面来一步步完成这道线程间通讯问题。微信

首先不考虑主线程和子线程之间的通讯,先把各个线程所要执行的任务写好:ide

public class TraditionalThreadCommunication {

	public static void main(String[] args) {
		//开启一个子线程
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				for(int i = 1; i <= 50; i ++) {
					
					synchronized (TraditionalThreadCommunication.class) {
						//子线程任务:执行10次 
						for(int j = 1;j <= 10; j ++) {
							System.out.println("sub thread sequence of " + j + ", loop of " + i);
						}	
					}
				}
				
			}
		}).start();
		
		//main方法即主线程
		for(int i = 1; i <= 50; i ++) {
			
			synchronized (TraditionalThreadCommunication.class) {
				//主线程任务:执行5次
				for(int j = 1;j <= 5; j ++) {
					System.out.println("main thread sequence of " + j + ", loop of " + i);
				}	
			}		
		}
	}
}
复制代码

如上,两个线程各有50次大循环,执行50次任务,子线程的任务是执行10次,主线程的任务是执行5次。为了保证两个线程间的同步问题,因此用了 synchronized 同步代码块,并使用了相同的锁:类的字节码对象。这样能够保证线程安全。可是这种设计不太好,就像我在上一节的死锁中写的同样,咱们能够把线程任务放到一个类中,这种设计的模式更加结构化,并且把不一样的线程任务放到同一个类中会很容易解决同步问题,由于在一个类中很容易使用同一把锁。因此把上面的程序修改一下:函数

public class TraditionalThreadCommunication {

	public static void main(String[] args) {
		Business bussiness = new Business(); //new一个线程任务处理类
		//开启一个子线程
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				for(int i = 1; i <= 50; i ++) {
					bussiness.sub(i);
				}
				
			}
		}).start();
		
		//main方法即主线程
		for(int i = 1; i <= 50; i ++) {
			bussiness.main(i);
		}
	}

}
//要用到的共同数据(包括同步锁)或共同的若干个方法应该归在同一个类身上,这种设计正好体现了高类聚和程序的健壮性。
class Business {

	public synchronized void sub(int i) {

		for(int j = 1;j <= 10; j ++) {
			System.out.println("sub thread sequence of " + j + ", loop of " + i);
		}	
	}
	
	public synchronized void main(int i) {

		for(int j = 1;j <= 5; j ++) {
			System.out.println("main thread sequence of " + j + ", loop of " + i);
		}
}
复制代码

通过这样修改后,程序结构更加清晰了,也更加健壮了,只要在两个线程任务方法上加上 synchronized 关键字便可,用的都是 this 这把锁。可是如今两个线程之间尚未通讯,执行的结果是主线程循环执行任务50次,而后子线程再循环执行任务50次,缘由很简单,由于有 synchronized 同步。oop

下面继续完善程序,让两个线程之间完成题目中所描述的那样通讯:this

public class TraditionalThreadCommunication {

	public static void main(String[] args) {
		Business bussiness = new Business(); //new一个线程任务处理类
		//开启一个子线程
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				for(int i = 1; i <= 50; i ++) {
					bussiness.sub(i);
				}
				
			}
		}).start();
		
		//main方法即主线程
		for(int i = 1; i <= 50; i ++) {
			bussiness.main(i);
		}
	}

}
//要用到共同数据(包括同步锁)或共同的若干个方法应该归在同一个类身上,这种设计正好体现了高雷剧和程序的健壮性。
class Business {
	private boolean bShouldSub = true;
	
	public synchronized void sub(int i) {
		while(!bShouldSub) { //若是不轮到本身执行,就睡
			try {
				this.wait(); //调用wait()方法的对象必须和synchronized锁对象一致,这里synchronized在方法上,因此用this
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		for(int j = 1;j <= 10; j ++) {
			System.out.println("sub thread sequence of " + j + ", loop of " + i);
		}	
		bShouldSub = false; //改变标记
		this.notify(); //唤醒正在等待的主线程
	}
	
	public synchronized void main(int i) {
		while(bShouldSub) { //若是不轮到本身执行,就睡
			try {
				this.wait();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		for(int j = 1;j <= 5; j ++) {
			System.out.println("main thread sequence of " + j + ", loop of " + i);
		}
		bShouldSub = true; //改变标记
		this.notify(); //唤醒正在等待的子线程
	}
}
复制代码

首先,先不说具体的程序实现,就从结构上来看,已经体会到了这种设计的好处了:主函数里不用修改任何东西,关于线程间同步和线程间通讯的逻辑全都在 Business 类中,主函数中的不一样线程只须要调用放在该类中对应的任务便可。体现了高类聚的好处。spa

再看一下具体的代码,首先定义一个 boolean 型变量来标识哪一个线程该执行,当不是子线程执行的时候,它就睡,那么很天然主线程就执行了,执行完了,修改了 bShouldSub 并唤醒了子线程,子线程这时候再判断一下 while 不知足了,就不睡了,就执行子线程任务,一样地,刚刚主线程修改了 bShouldSub 后,第二次循环来执行主线程任务的时候,判断 while 知足就睡了,等待子线程来唤醒。这样逻辑就很清楚了,主线程和子线程你一下我一下轮流执行各自的任务,这种节奏共循环50次。线程

另外有个小小的说明:这里其实用 if 来判断也是能够的,可是为何要用 while 呢?由于有时候线程会假醒(就好像人的梦游,明明正在睡,结果站起来了),若是用的是if的话,那么它假醒了后,就不会再返回去判断if了,那它就很天然的往下执行任务,好了,另外一个线程正在执行呢,啪叽一下就与另外一个线程之间相互影响了。可是若是是while的话就不同了,就算线程假醒了,它还会判断一下 while 的,可是此时另外一个线程在执行啊,bShouldSub 并无被修改,因此仍是进到 while 里了,又被睡了~因此很安全,不会影响另外一个线程!官方 JDK 文档中也是这么干的。

线程间通讯就总结到这吧~如有错误,欢迎指正,咱们一块儿进步。

也欢迎你们关注个人微信公众号:程序员私房菜。我会持续输出更多文章。

公众号
相关文章
相关标签/搜索