`
stone2oo6
  • 浏览: 25973 次
社区版块
存档分类
最新评论

线程笔记之并发同步

阅读更多

在应用编程中,我们会遇到下面这样的调用模型。。。

 



 当一个业务方法(begin)中顺序调用多个子业务方法(opertion1-N),且有些子业务方法比较耗时,那么自然而然完成这次调用所需要的时间就比较长了。对于这样的问题,通常情况下会从两个方面对其进行重构和调优:

 

  1. 单个方法调优,即针对operation1-N中比较耗时的方法进行重构已达到期望的效果
  2. 业务重组和方法重构,即对整个大的业务方法进行重组,找出合乎当前需求(功能和性能)的实现方式

1比较好理解,最常见的就是sql语句,IO和string buffer方面的调优;2则需要看具体的应用场景了。由于本文主要是侧重线程的并发与同步,所以我将例举一个比较特殊的场景(webservices 远程调用)。如下:

 


 

 

对照上述序列图,若每一个operation方法都将进行一次远程webservice调用,那么一次调用的代价就要包含网络通信方面的开销。如果网络延时很高,多次远程调用的代价就相当大了。那么该如果结合上面的第2点进行重构和调优呢?

 

  • 减少网络调用次数。 例如 operation1->API1,operation2->API2, 那么是否可以提供一个包含API1和API2的新API12供其调用呢?这样一次调用就可以达到目的。
  • 多线程调用。如,让operation1与operation2并发,这样调用所需要的时间则为max(cost(operation1), cost(operation2))。这样做会大大提高系统的复杂性,谨慎而为之。

 

接下来进入主题,谈谈并发的具体实现方式。

 

基本测试类ConcurrentSimpleTest

 

public class ConcurrentSimpleTest {

	public void method1() {
		System.out.println("before exec method1");
		try {
			Thread.sleep(400);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			System.out.println("method1 has been interrupted.");
			e.printStackTrace();
			
		}
		System.out.println("after exec method1");
	}

	public void method2() {
		System.out.println("before exec method2");
		try {
			Thread.sleep(800);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			System.out.println("method2 has been interrupted.");
			e.printStackTrace();
			
		}
		System.out.println("after exec method2");
	}
}

 

 

方式1:使用线程的join方法

public static void main(String[] args) throws InterruptedException {
		final ConcurrentSimpleTest cst = new ConcurrentSimpleTest();
		
		long s1 = System.currentTimeMillis();
		cst.method1(); 
		cst.method2();
		long s2 = System.currentTimeMillis();	
		
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				cst.method1(); 
			}
		});

		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				cst.method2();
			}
		});
		
		t1.start();
		t2.start();
		
		t1.join(); //t2.join(500); 实际400ms后,方法就返回了
		t2.join(); //t1.join(x); if x< max((800 - 400), (800-500)), 那该方法会在t2执行完前返回
		
		//线程1/2都已返回,需要验证结果
		
		long s3 = System.currentTimeMillis();
		
		System.out.println("time cost for normal execution:" + (s2-s1));
		System.out.println("time cost for concurrent execution:" + (s3-s2));
	}

 

 

方式2:使用信号量对象

 

  • 自定义信号量
public class SimpleMonitor {
		public volatile int single = 0;

		public void require(int single) {
			this.single = single;
		}

		public synchronized void release(int single) {
			this.single -= single;
			this.notify();
		}
	}
 

 

public static void main(String[] args) throws InterruptedException {
		final ConcurrentSimpleTest cst = new ConcurrentSimpleTest();
		final SimpleMonitor monitor = new SimpleMonitor();

		long s1 = System.currentTimeMillis();
		cst.method1();
		cst.method2();
		long s2 = System.currentTimeMillis();

		monitor.require(2); //初始化信号量
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					cst.method1();
				} finally {
					monitor.release(1); //信号量-1
				}
			}
		});

		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					cst.method2();
				} finally {
					monitor.release(1); //信号量-1
				}
			}
		});

		t1.start();
		t2.start();

		synchronized (monitor) {
			while (monitor.single > 0) {
				monitor.wait(); // monitor.wait(10 * 1000), 进行超时异常处理,中断t1,t2
			}
		}
                //线程1/2都已返回,需要验证结果
		long s3 = System.currentTimeMillis();

		System.out.println("time cost for normal execution:" + (s2 - s1));
		System.out.println("time cost for concurrent execution:" + (s3 - s2));
	}

 

 

  • 使用JDK concurrent包中的信号量对象Semaphores
public static void main(String[] args) throws InterruptedException {
		final ConcurrentSimpleTest cst = new ConcurrentSimpleTest();
		final Semaphore monitor = new Semaphore(0);

		long s1 = System.currentTimeMillis();
		cst.method1();
		cst.method2();
		long s2 = System.currentTimeMillis();

		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					cst.method1();
				} finally {
					monitor.release(); //增加信号量
				}
			}
		});

		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					cst.method2();
				} finally {
					monitor.release(); //增加信号量
				}
			}
		});

		t1.start();
		t2.start();

		monitor.acquireUninterruptibly(2); //tryAcquire(int permits, long timeout, TimeUnit unit) 可设置超时处理
		
                //线程1/2都已返回,需要验证结果

		long s3 = System.currentTimeMillis();

		System.out.println("time cost for normal execution:" + (s2 - s1));
		System.out.println("time cost for concurrent execution:" + (s3 - s2));
	}

 

 

  • 使用JDK concurrent包中的信号量对象CountDownLatch(似乎比Semaphores更适合这种场景)
public static void main(String[] args) throws InterruptedException {
		final ConcurrentSimpleTest22 cst = new ConcurrentSimpleTest22();
		final CountDownLatch monitor = new CountDownLatch(2); //设置计数器

		long s1 = System.currentTimeMillis();
		cst.method1();
		cst.method2();
		long s2 = System.currentTimeMillis();

		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					cst.method1();
				} finally {
					monitor.countDown(); //计数器-1
				}
			}
		});

		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					cst.method2();
				} finally {
					monitor.countDown(); //计数器-1
				}
			}
		});

		t1.start();
		t2.start();

		monitor.await(1, TimeUnit.SECONDS); //monitor.await(1000, TimeUnit.MILLISECONDS); 设定超时阀值
		//线程1/2都已返回,需要验证结果

		long s3 = System.currentTimeMillis();

		System.out.println("time cost for normal execution:" + (s2 - s1));
		System.out.println("time cost for concurrent execution:" + (s3 - s2));
	}
 

 

方式3:使用JDK5中新的线程实现方式和线程池

 

 

public static void main(String[] args) throws InterruptedException, ExecutionException {
		final ExecutorService execPool = Executors.newFixedThreadPool(5);
		final ConcurrentSimpleTest3 cst = new ConcurrentSimpleTest3();
	
		long s1 = System.currentTimeMillis();
		cst.method1();
		cst.method2();
		long s2 = System.currentTimeMillis();
		
		Callable<Void> call1 = new Callable<Void>(){
			@Override
			public Void call() throws Exception {
				cst.method1();
				return null;
			}			
		};
		
		Callable<Void> call2 = new Callable<Void>(){
			@Override
			public Void call() throws Exception {
				cst.method2();
				return null;
			}			
		};
		
		Future<Void> task1 = execPool.submit(call1);
		Future<Void> task2 = execPool.submit(call2);
		
		task1.get();//task1.get(1, TimeUnit.SECONDS); get方法会阻塞,直到线程执行结束返回结果或者超时
		task2.get();//task2.get(1, TimeUnit.SECONDS);	
		
                //线程1/2都已返回,需要验证结果

		long s3 = System.currentTimeMillis();

		System.out.println("time cost for normal execution:" + (s2 - s1));
		System.out.println("time cost for concurrent execution:" + (s3 - s2));
		
		execPool.shutdown();
	}
 

要达到目的,我们的实现的方式有多种,上面给出的例子也只是抛砖引入,那么该如何抉择呢?个人认为应该考虑以下几个方面:

 

  1. 并发线程(任务)的可控性。如线程执行是否超时,可中断执行中的线程,异常处理以及获取线程执行的状态等。方法1/2/3都针对具体的子线程(任务)可控,而方法2在超时设定方面则是针对并发的线程。
  2. 合理性,即合乎人的思维和设计,个人认为用信号量同步的方式(方法2)比较合理。
  3. 高效性,即性能。线程池应该是个不错的选择。
  4. 简化性,即实现和使用比较简单化。

简化,其实就是更多的复用/重用。对于上面给出的应用场景,可以提出下面这样的模型:


其中主线程为main thread,在它的执行过程中会启动2个子线程T1和T2,等待T1和T2执行结束后,main thread才能执行结束。

 

进一步抽象下,便可以得到下面这个复杂点的模型:


模型中每个背景为白色的节点代表一个Thread,边代表执行的步骤和方向。

 

Begin 会启动T1和T2 线程,T2 执行完毕后会执行T4,而从 T1 和 T2 指向 T3 的两条边表示的是 T3 必须等 T1 和 T2 都执行完毕以后才能开始执行。若T1和T2同时执行完毕,那么T3和T4也会并发执行。当T3和T4执行完成,就会执行End,整个过程结束。

 

对于这个模型,我们可以提供一个简单的框架示意图:



其具体实现主要分为3部分:

 

  1. Constructor: 线程(任务)构建器,主要构建子线程(任务)以及其关联关系(主要是前置依赖关系)
  2. Executor: 线程(任务)执行器,主要负责高效地执行所提交的线程(任务)
  3. Monitor: 负责监控一组线程(任务)的执行状态并进行简单地调度。如前置任务出现异常,那么后续任务该任何执行等等

当然还得提供一些额外的访问接口:如,线程(任务)的执行结果等。这样一个框架/组件的雏形就有了,而作为调用者,也只要关心输入和输出即可。

 

 

 

总结:本文主要从一个顺序调用的例子出发,因性能问题引申到了并发,列出了并发同步的几种简单实现示例,并最后提出了一个并发框架的雏形。

 

有兴趣的朋友可以一起讨论其具体实现和应用。

 

--《全文完》--

  • 大小: 13.6 KB
  • 大小: 18 KB
  • 大小: 16.6 KB
  • 大小: 12.3 KB
  • 大小: 29.8 KB
1
1
分享到:
评论

相关推荐

    Java分布式应用学习笔记05多线程下的并发同步器

    ### Java分布式应用学习笔记05多线程下的并发同步器 #### 1. 前言 在现代软件开发中,特别是在分布式系统和高性能计算领域,有效地管理多线程之间的协同工作至关重要。Java语言提供了丰富的工具和API来帮助开发者...

    java分布式应用学习笔记05多线程下的并发同步器.pdf

    本篇笔记将深入探讨Java中的并发同步机制,包括核心概念、工具类以及在实际开发中的应用。 首先,我们要理解什么是线程安全。线程安全是指在多线程环境下,一个方法或类能够正确处理多个线程同时访问的情况,不会...

    多线程与高并发编程笔记、源码等

    标题“多线程与高并发编程笔记、源码等”表明了资源的核心内容,涵盖了多线程和高并发编程的理论与实践。多线程允许一个应用程序同时执行多个任务,而高并发则指系统能够处理大量并发请求的能力。这两个概念在现代...

    张孝祥Java多线程与并发库高级应用笔记

    ### 张孝祥Java多线程与并发库高级应用笔记概览 #### 一、Java多线程技术的重要性与挑战 Java线程技术是软件工程领域不可或缺的一部分,尤其在底层编程、Android应用开发以及游戏开发中,其重要性不言而喻。然而,...

    马士兵多线程笔记.zip

    以下是对马士兵多线程笔记的详细解析。 1. **多线程基础**:多线程是指一个应用程序中同时执行多个线程(即任务)的能力。这种并发执行可以提高系统资源的利用率,提升程序的响应速度和执行效率,特别是在多核...

    Java分布式应用学习笔记03JVM对线程的资源同步和交互机制

    ### Java分布式应用学习笔记03:JVM对线程的资源同步和交互机制 在深入探讨Java虚拟机(JVM)如何处理线程间的资源同步与交互机制之前,我们先来明确几个关键概念:线程、多线程、同步、并发以及它们在Java中的实现...

    多线程学习笔记

    多线程学习笔记 iOS开发中,多线程是一种常见的技术手段,用于优化应用程序的性能,提升用户体验。多线程的核心是让程序能够并发地执行多个任务,合理地利用设备的计算能力,尤其是在拥有多个核心的处理器上。 ...

    java多线程笔记

    线程同步是为了避免多线程环境下的数据竞争问题,Java提供了多种同步机制。同步方法通过`synchronized`关键字修饰,确保同一时间只有一个线程能访问该方法。同步块(Synchronized Block)更灵活,可以指定同步的代码...

    马士兵多线程训练营笔记

    通过马士兵的多线程训练营笔记,开发者不仅可以学习到多线程的基本概念,还能掌握高级并发编程技巧,这对于开发高并发、高性能的应用至关重要。在阅读和学习这些笔记时,结合实际的编程练习将有助于更好地理解和巩固...

    线程总结笔记

    ### 线程总结笔记——基于Linux环境下的线程控制与同步 #### 一、引言 本篇“线程总结笔记”主要针对Linux环境下多线程编程中的关键概念进行了整理与归纳,尤其是针对线程同步的问题进行了深入探讨。通过一个具体...

    Java多线程详解(超详细)_狂神说笔记完整版_项目代码_适合小白随课程学习

    本教程将详细讲解Java中的多线程概念,包括线程的创建、状态、同步以及高级主题,旨在帮助初学者逐步掌握这一关键技能。 1. **线程简介** - 线程是操作系统分配CPU时间的基本单位,一个进程可以有多个线程,它们...

    多线程笔记

    1. **创建线程对象**:通过以上两种方式之一创建线程对象。 2. **调用线程对象的start()方法**:这是启动线程的标准方法。注意不要直接调用`run()`方法,这会导致线程行为如同普通方法调用。 ```java Thread ...

    java多线程笔记全手打

    通过阅读`多线程笔记.doc`和运行`threadDemo`示例代码,你可以对Java多线程有更深入的理解,并能够在实际项目中灵活运用这些知识,解决并发问题。同时,博客地址提供了更多详细内容,可以帮助你进一步探索和实践。

    JAVA 多线程学习笔记

    1. 同步机制:为了解决多线程并发访问共享资源导致的数据不一致问题,Java提供了synchronized关键字、Lock接口(如ReentrantLock)以及相关的并发工具类。 2. synchronized:用于修饰方法或代码块,实现互斥访问。...

    day06 【线程、同步】-笔记.pdf

    这些示例有助于理解Java中多线程运行的原理以及如何创建线程和控制线程同步。 通过理解上述知识点,我们可以更好地设计和实现多线程程序,确保程序在并发环境下既能够高效地执行多任务,也能够正确地处理共享数据,...

    C# 多线程笔记和示例讲解

    以下是对"C#多线程笔记"中可能包含的知识点的详细解释。 1. **线程基础**: - **什么是线程**:线程是程序执行的最小单元,每个进程至少有一个线程,负责执行程序代码。 - **主线程与子线程**:主线程是程序的...

    线程笔记(多线程,异常)

    线程是并发编程中的基本单位,它允许程序在同一时间处理多个任务。在.NET框架中,线程的管理和同步是至关重要的。以下是对标题和描述中提到的线程相关知识点的详细解释: 1. **Interrupt方法**:在.NET中,`Thread....

    java并发编程实践笔记

    ### Java并发编程实践笔记知识点详解 #### 一、保证线程安全的方法 ...以上是Java并发编程实践笔记中总结的关键知识点,涵盖了从基本概念到高级技术的应用,旨在帮助开发者构建高效、可靠的多线程应用。

    Java中线程同步和线程协作学习笔记

    在Java编程中,线程同步和线程协作是多线程编程的重要概念,确保了在并发环境下程序的正确性和数据的一致性。线程同步的主要目标是解决线程安全问题,即在多线程访问共享资源时避免数据的混乱,保证程序的可再现性。...

Global site tag (gtag.js) - Google Analytics