Java并发之传统线程同步通信技术代码详解

本文研究的主要是Java并发之传统线程同步通信技术的相关代码示例,具体介绍如下。

先看一个问题:

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

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

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

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同步。

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

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类中,主函数中的不同线程只需要调用放在该类中对应的任务即可。体现了高类聚的好处。

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

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

线程间通信就总结到这吧~

总结

以上就是本文关于Java并发之传统线程同步通信技术代码详解的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!

时间: 2018-02-05

详解Java利用同步块synchronized()保证并发安全

本文实例为大家分享了Java利用同步块synchronized()保证并发安全的具体代码,供大家参考,具体内容如下 package day10; /** * 同步块 * 有效地缩小同步范围 * 可以在保证并发安全的同时尽可能提高并发效率 * * 实例:模拟两个人同时进店买衣服,为提高效率 * 只在试衣服阶段进行同步排队过程,其他阶段无需排队. * @author kaixu * */ public class SyncDemo2 { public static void main(String[

浅谈Java并发 J.U.C之AQS:CLH同步队列

CLH同步队列是一个FIFO双向队列,AQS依赖它来完成同步状态的管理,当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态. 在CLH同步队列中,一个节点表示一个线程,它保存着线程的引用(thread).状态(waitStatus).前驱节点(prev).后继节点(next),其定义如下: static final class Node

Java中同步与并发用法分析

本文较为详细的分析了Java中同步与并发的用法.分享给大家供大家参考.具体分析如下: 1.同步容器类包括两部分:vector和hashtable 另一类是同步包装类,由Collections.synchronizedXXX创建.同步容器对容器的所有状态进行串行访问,从而实现线程安全. 它们存在如下问题: a) 对于符合操作,需要额外的锁保护.比如迭代,缺少则添加等条件运算. b) toString,hashCode,equals都会间接的调用迭代,都需要注意并发.   2.java5.0中的并发

Java从同步容器到并发容器的操作过程

引言 容器是Java基础类库中使用频率最高的一部分,Java集合包中提供了大量的容器类来帮组我们简化开发,我前面的文章中对Java集合包中的关键容器进行过一个系列的分析,但这些集合类都是非线程安全的,即在多线程的环境下,都需要其他额外的手段来保证数据的正确性,最简单的就是通过synchronized关键字将所有使用到非线程安全的容器代码全部同步执行.这种方式虽然可以达到线程安全的目的,但存在几个明显的问题:首先编码上存在一定的复杂性,相关的代码段都需要添加锁.其次这种一刀切的做法在高并发情况下性

Java同步容器和并发容器详解

同步容器 在 Java 中,同步容器主要包括 2 类: Vector.Stack.HashTableCollections 类中提供的静态工厂方法创建的类(由 Collections.synchronizedXxxx 等方法) Collections类中提供的静态工厂方法创建的类 Vector 实现了 List 接口,Vector 实际上就是一个数组,和 ArrayList 类似,但是Vector 中的方法都是 synchronized 方法,即进行了同步措施. Stack 也是一个同步容器,它

java并发编程之同步器代码示例

同步器是一些使线程能够等待另一个线程的对象,允许它们协调动作.最常用的同步器是CountDownLatch和Semaphore,不常用的是Barrier和Exchanger 队列同步器AbstractQueuedSynchronizer是用来构建锁或者其他同步组件的基础框架,它内部使用了一个volatiole修饰的int类型的成员变量state来表示同步状态,通过内置的FIFO队列来完成资源获取线程的排队工作. 同步器的主要使用方式是继承,子类通过继承同步器并实现它的抽象方法来管理同步状态,在抽

Java多线程编程实现socket通信示例代码

流传于网络上有关Java多线程通信的编程实例有很多,这一篇还算比较不错,代码可用.下面看看具体内容. TCP是Tranfer Control Protocol的 简称,是一种面向连接的保证可靠传输的协议.通过TCP协议传输,得到的是一个顺序的无差错的数据流.发送方和接收方的成对的两个socket之间必须建 立连接,以便在TCP协议的基础上进行通信,当一个socket(通常都是server socket)等待建立连接时,另一个socket可以要求进行连接,一旦这两个socket连接起来,它们就可以

Java编程异常简单代码示例

练习1 写一个方法void triangle(int a,int b,int c),判断三个参数是否能构成一个三角形.如果不能则抛出异常IllegalArgumentException,显示异常信息:a,b,c "不能构成三角形":如果可以构成则显示三角形三个边长.在主方法中得到命令行输入的三个整数,调用此方法,并捕获异常. 两边之和大于第三边:a+b>c 两边之差小于第三边:c-a package 异常; import java.util.Arrays; import java

java编程队列数据结构代码示例

队列是一种特殊的线性表,只允许在表的前端进行删除,在表的后端进行插入,表的前端称为(front)队头,表的后端称为(rear)队尾. 所以队列跟生活的场景很是相似,在电影院买电影票,人们排成一排,第一个人进入队尾最先到达队头后买票进入影院,后面排队的人按照排队的次序买到票后进入影院. 所以 队列是一种先进先出的数据结构(FIFO). 编程实现对循环链队列的入队和出队操作. ⑴根据输入的队列长度n和各元素值建立一个带头结点的循环链表表示的队列(循环链队列),并且只设一个尾指针来指向尾结点,然后输出

Java并发编程Callable与Future的应用实例代码

本文主要探究的是java并发编程callable与future的使用,分享了相关实例代码,具体介绍如下. 我们都知道实现多线程有2种方式,一种是继承Thread,一种是实现Runnable,但这2种方式都有一个缺陷,在任务完成后无法获取返回结果.要想获得返回结果,就得使用Callable,Callable任务可以有返回值,但是没法直接从Callable任务里获取返回值:想要获取Callabel任务的返回值,需要用到Future.所以Callable任务和Future模式,通常结合起来使用. 试想

Java并发编程Semaphore计数信号量详解

Semaphore 是一个计数信号量,它的本质是一个共享锁.信号量维护了一个信号量许可集.线程可以通过调用acquire()来获取信号量的许可:当信号量中有可用的许可时,线程能获取该许可:否则线程必须等待,直到有可用的许可为止. 线程可以通过release()来释放它所持有的信号量许可(用完信号量之后必须释放,不然其他线程可能会无法获取信号量). 简单示例: package me.socketthread; import java.util.concurrent.ExecutorService;

Java并发编程(CyclicBarrier)实例详解

Java并发编程(CyclicBarrier)实例详解 前言: 使用JAVA编写并发程序的时候,我们需要仔细去思考一下并发流程的控制,如何让各个线程之间协作完成某项工作.有时候,我们启动N个线程去做一件事情,只有当这N个线程都达到某一个临界点的时候,我们才能继续下面的工作,就是说如果这N个线程中的某一个线程先到达预先定义好的临界点,它必须等待其他N-1线程也到达这个临界点,接下来的工作才能继续,只要这N个线程中有1个线程没有到达所谓的临界点,其他线程就算抢先到达了临界点,也只能等待,只有所有这N

Java 并发编程之线程挂起、恢复与终止

挂起和恢复线程 Thread 的API中包含两个被淘汰的方法,它们用于临时挂起和重启某个线程,这些方法已经被淘汰,因为它们是不安全的,不稳定的.如果在不合适的时候挂起线程(比如,锁定共享资源时),此时便可能会发生死锁条件--其他线程在等待该线程释放锁,但该线程却被挂起了,便会发生死锁.另外,在长时间计算期间挂起线程也可能导致问题. 下面的代码演示了通过休眠来延缓运行,模拟长时间运行的情况,使线程更可能在不适当的时候被挂起: public class DeprecatedSuspendResume

Java 并发编程学习笔记之核心理论基础

并发编程是Java程序员最重要的技能之一,也是最难掌握的一种技能.它要求编程者对计算机最底层的运作原理有深刻的理解,同时要求编程者逻辑清晰.思维缜密,这样才能写出高效.安全.可靠的多线程并发程序.本系列会从线程间协调的方式(wait.notify.notifyAll).Synchronized及Volatile的本质入手,详细解释JDK为我们提供的每种并发工具和底层实现机制.在此基础上,我们会进一步分析java.util.concurrent包的工具类,包括其使用方式.实现源码及其背后的原理.本

Java并发编程预防死锁过程详解

这篇文章主要介绍了Java并发编程预防死锁过程详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 在java并发编程领域已经有技术大咖总结出了发生死锁的条件,只有四个条件都发生时才会出现死锁: 1.互斥,共享资源X和Y只能被一个线程占用 2.占有且等待,线程T1已经取得共享资源X,在等待共享资源Y的时候,不释放共享资源X 3.不可抢占,其他线程不能强行抢占线程T1占有的资源 4.循环等待,线程T1等待线程T2占有的资源,线程T2等待线程T1占有