`
suhuanzheng7784877
  • 浏览: 702337 次
  • 性别: Icon_minigender_1
  • 来自: 北京
博客专栏
Ff8d036b-05a9-33b5-828a-2633bb68b7e6
读金庸故事,品程序人生
浏览量:47699
社区版块
存档分类
最新评论

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

阅读更多

1.  前言

既然是分布式系统,就离不开对于多线程程序的开发,面对客户端大并发的访问,如何控制程序的多线程资源?我们都知道在程序中使用关键字synchronized,对对象级别的加锁也好,对类级别的加锁也罢。JVM在底层是如何运行的,这个属于JVM处理多线程的原理了,当然了,JVM最终当然还是需要操作系统和CPU一起完成真正的多线程并发的问题。只是咱们这次放慢时间,看看JVM这一层对于多线程并发机制是如何做处理的。

不同的线程之间一般出现的交互关系有:竞争,也可以称为互斥;交互,也可以称为共享协作。

2.  旧话重提——何为线程不安全

这其实是一个老生常谈的问题了。还是那句老话,线程不安全发生在单例或者多例的情况下,如果每次访问服务代码都new一个新的对象,此新对象所有非静态的东东都是指向内存中不同的地址段。换句话说就是你干你的,我干我的。井水不犯河水,你走你的独木桥,我有我的阳关道,何来线程安全问题。下面是一个单例模式下的代码片段:

	int i=0;
	public void add(){
		this.i ++ ;
	}
 如果在同一个时刻,客户端有多个人同时调用了此代码块的add方法。会出现i的值可能会出现与预期结果不符的现象。咱们将时间放慢,就用显微镜看看JVM是如何处理this.i ++ ;这简简单单的一行代码的。

1.  首先在JVM的堆区(main memory)区域分配给i一个内存存储场所,并存储其值为初始值0

2.  一个客户端发起调用,线程启动后分配了一个区域操作数栈区域(working memory)当线程执行到了this.i++时候,JVM细节上在working memory做着5个步骤

3.  装载i,线程发起一个请求,让JVM执行引擎像堆区发一个读取的指令

4.  读取i,读取指令开始执行,从堆开始读取ii从堆复制到working memory

5.  进行i++操作,线程完成相加指令

6.  存储i,将i++的结果赋值给i变量,之后在存储到working memory区域

7.  写入i,将i的结果值写回到刚刚的堆区(main memory

3~7这几步骤中虽然时间极其短,但是高并发下,一定概率还是能发生线程不安全的问题的。

JVM堆区(main memory)通常的操作有:readwritelockunlock

read:从堆读取变量的值。

write:将working memory的值写回到堆中的变量值。

lock:由线程发起,同步操作堆区,将堆中的对象上锁。

unlock:也由线程发起,去除对象上的锁。前提是线程掌握了该对象的锁。

working memory区域,其实也是真正的指令工作区域,一般有以下一些操作:useuseassignloadstorelockunlocklockunlock上面已经说过了,我们看看其他的指令是什么意思。

use:由线程发起,将working memory区域的变量值复制到JVM执行引擎中

assign:由线程发起,将变量值复制到working memory区域。a=i,相当于线程发起了一个assign指令。

load:将堆中read到的值复制到working memory中。

store:负责将working memory区域的值返回复制给堆区。

这些指令就是三大区域:堆、指令工作区(就是上面一直称之为working memory的东东)、JVM执行引擎区交互的指令,利用这些指令,我们程序中的变量值才能发生变化。

如果这个代码块处在一个每次请求都new一个对象出来的情况下有线程安全问题吗,答案当然是:“没有”。i变量属于局部变量每次new一个对象出来,对象指针指向内存新的地址区域,对象内局部变量也是指向新的内存位置。

3.  线程资源竞争机制

既然上面的程序有了线程安全问题,那么我们怎么解决呢?

有多种解决方案:加同步关键字、使用ThreadLocal进行副本操作、使用new…………。

一般集中在前两者,前者是用时间换取空间,加锁,阻塞。后者是以空间换时间,使用变量副本。我们在此只讨论加锁、互斥、阻塞的synchronized

	public void add() {
		synchronized (this) {
			this.i++;
		}
	}

 这个几乎是家喻户晓了,在该线程的对象上加锁,拿到传国玉玺,挟天子以令诸侯,别的诸侯谁也别想下圣旨,皇帝在我(当前执行线程)手里呢。执行i++后该线程释放对此对象的持有锁,交出玉玺,也该让别人过过类似曹操的隐了吧。这就是所谓的互斥,保证了在同一个时间段,对同步对象进行加锁后,别人就在执行队列中等待着,再来一个君主,看到曹操还没爽够呢,得了,和刘备一起在执行队列中等着吧,等曹操爽够了,皇帝、玉玺没用了,交出锁,释放对象锁。根据队列的先进先出原则,按道理是该刘备抢到玉玺,也过把隐!

还有lockunlock方法和synchronized功能类似。一般情况下使用的概率较少,因为得成对出现。

	private Lock lock = new ReentrantLock();
	public void add() {
		lock.lock();
		this.i++;
		lock.unlock();
	}

 volatile修饰变量,虽然减少了线程不安全的概率,但是呢不能从根本上完全解除。

volatile int i = 0;

 因为用volatile修饰的变量,是直接在堆区进行操作,根本就不复制到操作工作栈。节省了每条代码的中间过程。

详细请查看http://www.ibm.com/developerworks/cn/java/j-jtp06197.html

4.  线程资源交互机制

线程之间除了互斥关系外,还有协作交互的关系。打个比喻,曹操假意汉献帝诏书,下诏让刘备去征讨袁术,刘备说:“曹公,没有汉献帝的玉玺,百姓不信是皇帝下的诏书,除非您将皇帝的玉玺给我,带到淮南,让百姓们也见见咱们是真正的奉诏讨贼!”。曹操说:“善”,之后将玉玺交出,刘备带着玉玺去征讨袁术这个伪皇帝,造成一种现象就是在刘备征讨成功前,曹操没有玉玺用,下不了诏书,刘备回来后归还玉玺给曹操,曹操才能继续挟天子以令诸侯。线程的协作交互机制也是大家耳熟能详的wait方法和notify方法。如下代码

package thread;

/**
 * 
 * @author liuyan
 */
public class TestNotify {
	public static void main(String[] args) {

		TestNotify testNotify = new TestNotify();

		ThreadB b = testNotify.new ThreadB();
		
		System.out.println("b is start");

		b.start();
		
/*
		try {
			Thread.currentThread().sleep(5);
		} catch (InterruptedException e1) {
			// TODO Auto-generated catch block
			e1.printStackTrace();
		}
*/
		
		synchronized (b) {

			try {

				System.out.println("Waiting for b to complete");
				
				// 暂时放弃对象锁,让主线程暂停,让ThreadB开始执行
				b.wait(); 

			} catch (InterruptedException e) {

				e.printStackTrace();
			}

			System.out.println("Final Total is:" + b.total);

		}

	}

	class ThreadB extends Thread {

		int total;

		public void run() {

			synchronized (this) {

				System.out.println("ThreadB is running");

				for (int i = 0; i < 100; i++) {
					total += i;
				}
				
				// 执行完毕,唤醒被暂停的线程
				notify(); 
			}
		}
	}
}

 在主线程启动新线程b,主线程与新线程同时run,继续往下走。主线程相当于曹操,线程b相当于刘备。主线程使用wait方法交出b对象的持有锁,等待线程b使用完成后释放对象锁,主线程才能继续拥有对象b的对象锁,继续往下走自己的路。

从严格意义上来讲,可能会出现线程b抢在主线程前抢到b的锁,执行,线程b用完后将锁交出去,问题是此时等待集合wait set并没有任何线程元素。之后主线程一直执行到wait操作等着那个永远不会唤醒它的那个“人”。各位可以将注释那段等待的代码释放,扩大此事件的发生概率,主线程一定会发生永远睡眠、永远等待被唤醒的郁闷状态,等待永远是痛苦的,尤其是这种没有结果的等待。因为主线程运行的优先级和资源抢占比新启动的线程要高,所以可以说不加主线程睡眠的代码片段,99.9999999%的几率不会出现以上那种郁闷现象。notify是随机在等待集合中跳出一个线程将其唤醒。notifyAll方法是将等待集合中所有的线程都唤醒。

5.  线程的运行状态

线程运行状态分为几个阶段:

新生阶段:尚未启动,还在酝酿准备启动的阶段,也就是还未start的阶段。

可运行状态:这是代表调用了start方法或者是线程被唤醒,线程就进入可运行状态,线程对象进入到可运行池中。

运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。这也是线程进入运行状态的唯一一种方式。

等待/阻塞/睡眠状态:这是线程有资格运行时它所处的状态。实际上这个三状态组合为一种,其共同点是:线程仍旧是活的,但是当前没有条件运行。换句话说,它是可运行的,但是如果某件事件出现,他可能返回到可运行状态。

死亡态:当线程的run()方法完成时就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。

 

6.  总结

这次相当于又复习了一下J2SE方面的线程的运行原理和互斥、交互机制。无论在Java的哪个领域,多线程永远是个活跃的话题。在分布式Java系统应用也不例外,甚至对多线程研发人员的要求要苛刻得多。线程安全和多线程的调试也是十分热门的话题。写程序时刻有并发下该程序还能否正常运行、性能是否会很差的疑问思维总是很好的。

PS:写完后才意识到一件事,袁术称帝的时候是真玉玺!刘备就算那玉玺去征讨袁术,也是假玉玺。呵呵,各位不必认真,打个比方罢了。

  • 大小: 29.9 KB
26
21
分享到:
评论
10 楼 ma_xuezhi1987 2011-07-30  
赞一个
9 楼 suhuanzheng7784877 2011-07-29  
uuid198909 写道
天朝虽不容我,心安即是归处

呵呵
8 楼 uuid198909 2011-07-29  
天朝虽不容我,心安即是归处
7 楼 zhxh007 2011-07-29  
6 楼 suhuanzheng7784877 2011-07-29  
uuid198909 写道
我不知道别人看得懂看不懂,[b]反正我看懂了。[/b]


呵呵,兄弟,你这评论确实与时俱进啊~~当心被和谐了啊
5 楼 suhuanzheng7784877 2011-07-29  
tobeno1 写道
博主好,我最近很困惑一个分布式的问题。如果在多个JVM上的程序,如何保证数据的安全性。现在我想到的只能是在数据库上进行一些控制

恩,兄弟,你提问的问题,我的理解是这样,你所说的数据主要还是数据库记录的数据是吧?而你是从多台机器操作数据库的是吧?时候怕多台机器线程之间对数据库记录操作不安全?
基于以上问题有很多解决方案:
1.利用数据库锁,数据库本身的锁机制,在hibernate中叫做悲观锁!别的线程、进程不允许操作
2.乐观锁:就是给记录加上version属性,限制操作,每次进行写操作都要进行version判断
3.如果不是数据数据库级别的记录,而是多线程中对象、变量进行并发计算,那么需要用到java并发包中的线程同步器、调度器,这个我之后的blog会有总结的。叫做,《Java分布式应用学习笔记05多线程下的并发同步器》。专门用于集群高并发下的那些类。
4.如果兄弟你等不及了,你可以看看hadoop相关资料。

不知道我描述的怎么样,希望能够帮到你。
4 楼 tobeno1 2011-07-29  
博主好,我最近很困惑一个分布式的问题。如果在多个JVM上的程序,如何保证数据的安全性。现在我想到的只能是在数据库上进行一些控制
3 楼 uuid198909 2011-07-29  
我不知道别人看得懂看不懂,[b]反正我看懂了。[/b]
2 楼 suhuanzheng7784877 2011-07-28  
你若无情我便休 写道
very good.up

iteye现在见贴就踩,唉~~~~无奈……
1 楼 你若无情我便休 2011-07-28  
very good.up

相关推荐

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

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

    Java分布式应用学习笔记-谈JVM.doc

    【Java分布式应用学习笔记-谈JVM】 在Java分布式应用中,JVM(Java虚拟机)扮演着至关重要的角色。虽然有些人可能认为分布式系统与JVM的关系并不密切,但事实上,尤其是在大型分布式环境,如云计算服务平台,对Java...

    java学习笔记,学习java的好帮手

    IO流是处理数据输入和输出的关键,多线程让程序可以同时执行多个任务,集合框架提供了一种高效管理对象集合的方式,而网络编程则是构建分布式应用的基础。 在深入学习Java的过程中,了解JVM(Java虚拟机)的工作...

    黑马java教程知识点笔记整理

    【Java编程基础】 ...以上是黑马Java教程知识点笔记的主要内容,涵盖了Java编程的基础到进阶知识,是学习和巩固Java技能的重要参考资料。通过深入理解和实践这些知识点,开发者可以逐步提升Java编程能力。

    java 学习笔记大全

    Java是一种广泛使用的面向对象的...这份"java 学习笔记大全"应该覆盖了以上所有知识点,通过学习和实践,你可以掌握Java编程的核心技能,无论是进行简单的控制台程序开发,还是复杂的分布式系统设计,都将游刃有余。

    《java学习》-Java 学习笔记.zip

    - **多线程**:Java内置对多线程的支持,包括线程的创建、同步、互斥等机制。 - **Swing与AWT**:Java的图形用户界面(GUI)开发库,用于创建桌面应用程序。 3. **JavaEE(企业版)** - **Servlet与JSP**:在...

    Java后端学习笔记代码Java后端学习笔记代码

    本压缩包"Java后端学习笔记代码"显然是一个针对Java后端开发的学习资源集合,包含了作者在学习过程中编写的代码示例和笔记。以下是基于这个主题的详细知识点解析: 1. **Java基础**: 作为Java后端开发的起点,掌握...

    java学习笔记JDK6课件之一

    扩展了Java SE的功能,提供了如JavaServer Pages (JSP)、Servlet、Enterprise JavaBeans (EJB)、Java Remote Method Invocation (RMI)等服务和API,支持分布式、多层结构的Web应用程序开发。 Java ME则专注于小型...

    java入门基础学习笔记

    Java语言的特性包括简单性、面向对象、分布式、解释性、稳健性、安全性、体系结构中立性、可移植性、高性能、多线程和动态性等。为了能够运行Java程序,我们需要搭建相应的Java语言环境,包括JDK和JRE,并且需要正确...

    java学习笔记

    - **多线程编程**: 处理并发问题的技术,如线程的创建、同步和生命周期管理。 #### 五、Java EE内容 Java EE是针对企业级应用的一套完整的解决方案,其内容包括但不限于: - **基础**: - 面向对象编程 - 数据库...

    Java课程笔记

    - **原理:** Java内置了对多线程的支持,允许程序同时执行多个任务。 - **优势:** 改善了应用程序的交互性和响应性,特别是在图形用户界面和网络编程中表现突出。 **8. 高性能:** - **实现方式:** Java采用即时...

    Java_se基础毕向东老师全程笔记

    根据给定文件的信息,我们可以总结出“Java_SE基础毕向东老师全程笔记”中涉及的...以上是对毕向东老师全程笔记的总结,覆盖了Java基础知识到高级特性的各个方面,对于初学者和进阶学习者都是非常有价值的参考资料。

    Java笔记-基础部分

    Java的设计目标包括了简单性、安全可靠性、面向对象、分布式计算、平台无关性、多线程和动态性。这些特性使得Java在电子商务、金融、证券等多个行业中广泛应用,例如通过J2SE(Java2 Standard Edition)、J2EE(Java...

    java私塾基础入门笔记

    - **多线程**:支持并发执行,提高程序的效率和响应速度。 - **分布式**:易于创建分布式网络应用。 - **安全性**:内置的安全机制可以防止恶意代码的执行。 - **高性能**:通过即时编译器(JIT Compiler)优化运行...

    java笔记郝斌.pdf

    线程同步和互斥是多线程编程中的关键问题,Java提供了synchronized关键字、wait()、notify()和notifyAll()等工具来解决。 七、网络编程 Java提供了丰富的API进行网络编程,如Socket和ServerSocket类用于TCP连接,...

    java笔记.rar

    线程同步机制如synchronized关键字、wait()、notify()和notifyAll()方法,避免了多线程环境下的数据竞争问题。 9. **网络编程**:Java提供了丰富的Socket类和ServerSocket类,用于实现客户端-服务器通信。此外,...

    java笔记

    Java内置了对多线程的支持,通过Thread类或Runnable接口可以创建并管理线程。线程同步和互斥机制(如synchronized关键字、wait/notify机制)用于避免并发问题,确保程序的正确性。 此外,Java还提供了丰富的API库,...

    J2SE,J2EE学习笔记

    **Java技术体系与J2SE** Java技术体系主要包括三个主要...这份笔记不仅适合Java初学者,也对有经验的开发者提供了一种复习和深化理解的资源。通过深入学习和实践,开发者可以在Java平台上构建复杂且健壮的应用系统。

    Java并发编程与高并发解决方案-学习笔记-www.itmuch.com.pdf

    本文将基于文档《Java并发编程与高并发解决方案-学习笔记***.pdf》中提供的内容,来详细阐述并发编程和高并发的基本概念、CPU多级缓存与缓存一致性、以及Java内存模型。 ### 并发与高并发概念 在现代多线程编程中...

Global site tag (gtag.js) - Google Analytics