- 浏览: 691614 次
- 性别:
- 来自: 北京
博客专栏
-
读金庸故事,品程序人生
浏览量:47234
文章分类
最新评论
-
hty881008:
LZ,你的json返回是怎么出来的,我的怎么是No messa ...
使用CXF暴露您的REST服务 -
jxFY:
赞
Apache的对象池化工具commons-pool -
wangyudong:
新版本的Wisdom RESTClient地址https:// ...
使用CXF暴露您的REST服务 -
wangyudong:
由CXF实现的微服务需要有比较好的工具去测试RESTful A ...
使用CXF暴露您的REST服务 -
spring_springdata:
可以参考最新的文档:如何在eclipse jee中检出项目并转 ...
Maven3实战笔记01环境配置与使用入门
1. 前言
既然是分布式系统,就离不开对于多线程程序的开发,面对客户端大并发的访问,如何控制程序的多线程资源?我们都知道在程序中使用关键字synchronized,对对象级别的加锁也好,对类级别的加锁也罢。JVM在底层是如何运行的,这个属于JVM处理多线程的原理了,当然了,JVM最终当然还是需要操作系统和CPU一起完成真正的多线程并发的问题。只是咱们这次放慢时间,看看JVM这一层对于多线程并发机制是如何做处理的。
不同的线程之间一般出现的交互关系有:竞争,也可以称为互斥;交互,也可以称为共享协作。
2. 旧话重提——何为线程不安全
这其实是一个老生常谈的问题了。还是那句老话,线程不安全发生在单例或者多例的情况下,如果每次访问服务代码都new一个新的对象,此新对象所有非静态的东东都是指向内存中不同的地址段。换句话说就是你干你的,我干我的。井水不犯河水,你走你的独木桥,我有我的阳关道,何来线程安全问题。下面是一个单例模式下的代码片段:
1. 首先在JVM的堆区(main memory)区域分配给i一个内存存储场所,并存储其值为初始值0。 2. 一个客户端发起调用,线程启动后分配了一个区域操作数栈区域(working memory)当线程执行到了this.i++时候,JVM细节上在working memory做着5个步骤 3. 装载i,线程发起一个请求,让JVM执行引擎像堆区发一个读取的指令 4. 读取i,读取指令开始执行,从堆开始读取i,i从堆复制到working memory区 5. 进行i++操作,线程完成相加指令 6. 存储i,将i++的结果赋值给i变量,之后在存储到working memory区域 7. 写入i,将i的结果值写回到刚刚的堆区(main memory) 在3~7这几步骤中虽然时间极其短,但是高并发下,一定概率还是能发生线程不安全的问题的。 在JVM堆区(main memory)通常的操作有:read、write、lock、unlock。 read:从堆读取变量的值。 write:将working memory的值写回到堆中的变量值。 lock:由线程发起,同步操作堆区,将堆中的对象上锁。 unlock:也由线程发起,去除对象上的锁。前提是线程掌握了该对象的锁。 在working memory区域,其实也是真正的指令工作区域,一般有以下一些操作:use、use、assign、load、store、lock、unlock。lock与unlock上面已经说过了,我们看看其他的指令是什么意思。 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…………。 int i=0;
public void add(){
this.i ++ ;
}
如果在同一个时刻,客户端有多个人同时调用了此代码块的add方法。会出现i的值可能会出现与预期结果不符的现象。咱们将时间放慢,就用显微镜看看JVM是如何处理this.i ++ ;这简简单单的一行代码的。
public void add() { synchronized (this) { this.i++; } }
这个几乎是家喻户晓了,在该线程的对象上加锁,拿到传国玉玺,挟天子以令诸侯,别的诸侯谁也别想下圣旨,皇帝在我(当前执行线程)手里呢。执行i++后该线程释放对此对象的持有锁,交出玉玺,也该让别人过过类似曹操的隐了吧。这就是所谓的互斥,保证了在同一个时间段,对同步对象进行加锁后,别人就在执行队列中等待着,再来一个君主,看到曹操还没爽够呢,得了,和刘备一起在执行队列中等着吧,等曹操爽够了,皇帝、玉玺没用了,交出锁,释放对象锁。根据队列的先进先出原则,按道理是该刘备抢到玉玺,也过把隐!
还有lock和unlock方法和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:写完后才意识到一件事,袁术称帝的时候是真玉玺!刘备就算那玉玺去征讨袁术,也是假玉玺。呵呵,各位不必认真,打个比方罢了。
评论
呵呵
呵呵,兄弟,你这评论确实与时俱进啊~~当心被和谐了啊
恩,兄弟,你提问的问题,我的理解是这样,你所说的数据主要还是数据库记录的数据是吧?而你是从多台机器操作数据库的是吧?时候怕多台机器线程之间对数据库记录操作不安全?
基于以上问题有很多解决方案:
1.利用数据库锁,数据库本身的锁机制,在hibernate中叫做悲观锁!别的线程、进程不允许操作
2.乐观锁:就是给记录加上version属性,限制操作,每次进行写操作都要进行version判断
3.如果不是数据数据库级别的记录,而是多线程中对象、变量进行并发计算,那么需要用到java并发包中的线程同步器、调度器,这个我之后的blog会有总结的。叫做,《Java分布式应用学习笔记05多线程下的并发同步器》。专门用于集群高并发下的那些类。
4.如果兄弟你等不及了,你可以看看hadoop相关资料。
不知道我描述的怎么样,希望能够帮到你。
iteye现在见贴就踩,唉~~~~无奈……
发表评论
-
Java分布式应用学习笔记09JMX-MBean的介绍(JMX的一点点补充)
2011-10-09 09:01 73521. MBean介绍 从上一篇B ... -
Java分布式应用学习笔记08JMX规范与常用的监控场景
2011-09-13 09:17 89891. JMX规范 JMX是“Java管理扩展的”的缩写,它 ... -
Java分布式应用学习笔记07线程池应用(又名:线程池与大排档)
2011-09-07 09:00 71421. 线程池是啥子 一说到池子,大家都会想到数据库连接池那 ... -
Java分布式应用学习笔记06浅谈并发加锁机制分析
2011-08-19 16:12 80251. 前言 之前总结的多线程的调度、并发调度、线程加锁安全 ... -
Java分布式应用学习笔记05多线程下的并发同步器----后篇
2011-08-11 09:07 69685. CountDownLatch 很多资料上都说Coun ... -
Java分布式应用学习笔记05多线程下的并发同步器----前篇
2011-08-11 09:02 92671. 前言 JDK提供的并发包,除了上一篇提到的用于集合外 ... -
Java分布式应用学习笔记04JDK的并发包的集合总结---后篇
2011-08-02 17:21 4429唉~这一大篇blog又是只能显示部分,部分内容被截断了。。。。 ... -
Java分布式应用学习笔记04JDK的并发包的集合总结---前篇
2011-08-02 17:17 53131. 前言 平时咱们使用的HashMap、ArrayLis ... -
Java分布式应用学习笔记02再谈JVM---续
2011-07-25 09:22 3773唉~~因为blog总显示不全只能分为2个了,排版也不是很好,凑 ... -
Java分布式应用学习笔记02再谈JVM
2011-07-25 09:10 58211. 前言-为何要再谈JVM 很多人认为,分布式Java应 ... -
Java分布式应用学习笔记01分布式Java应用和SOA
2011-07-22 13:52 42021. 前言 当我们所做的 ... -
使用Memcached做分布式系统的Session存储
2011-07-01 10:12 84581. 前言 Memcache除了可以做Hibernate的 ... -
用xmemcache作为JPA(Hibernate实现)二级缓存
2011-06-30 09:44 59961. 持久层的缓存 Hibern ... -
Apache_proxy负载均衡和Session复制
2011-04-06 09:26 10772今天上网查了查资料,之前使用apache的jk模块做负载均衡。 ... -
Java基于线程的分布式(转自cjnetwork)
2011-03-22 17:50 1662java基于线程的分布式 ... -
JBoss集群配置的Session复制
2011-03-21 09:19 59161. 前言 接着上一篇总结文章提出的问题,这次通 ... -
JBoss节点的负载均衡与Mysql主从备份
2011-03-16 22:37 24321. 前言 做JavaEE企业级应用就离不开集群 ... -
在default目录下快速配置JBoss集群(Web方面) 转载
2011-03-06 10:24 2164说起JBoss集群好像很高深的样子,其实一点也不恐怖,建立一个 ...
相关推荐
Java分布式应用学习笔记03JVM对线程的资源同步和交互机制
Java分布式应用学习笔记-谈JVM
Java分布式应用学习笔记02再谈JVM
java 查看JVM中所有的线程的活动状况 java 查看JVM中所有的线程的活动状况
Java学习笔记包含JVM、spring、源码分析、多线程、offer题解、设计模式、面试宝典.zip Java学习笔记,内容包括JVM,spring,hashMap实现源码分析,多线程,剑指offer题解,设计模式。然后根据面试的重点,又将很多从...
本书章节包括分布式java应用,大型分布式java应用与SOA,深入理解jvm,分布式应用与sun jdk类库,性能调优,构件高可用的系统等,同时也适合面试考察的知识点。
JVM负责装载class文件并执行,因此,首先是JDK如何将Java代码编译为class文件、如何...JVM提供了多线程支持,对于分布式Java应用而言,通常要借助线程来实现高并发,JVM中线程资源如何同步的机制及线程之间交互的机制。
jvm和多线程基础知识分享,可以作为面试材料
java进阶提高学习教程-13JVM与多线程
Java 虚拟机学习笔记: Java 内存区域, 垃圾收集, 内存分配与回收策略, JVM 调优, 文件结构, 类加载机制, Java 程序 Java是一种面向对象的编程语言,由Sun Microsystems于1995年推出。它是一种跨平台的语言,...
该文主要讲解JVM和多线程相关内容,java八股文。
java之jvm学习笔记八(实践对jar包的代码签名)
java之jvm学习笔记十一(访问控制器)-源码
《Java JDK7学习笔记》是作者多年来教学实践经验的总结,汇集了教学过程中学生在学习java时遇到的概念、操作、应用或认证考试等问题及解决方案。《Java JDK7学习笔记》针对java se 7新功能全面改版,无论是章节架构...
这是Java学习过程中整理搜集的关于JVM,多线程,数据结构,集合的思维导图,能够有效的进行系统的学习
java之jvm学习笔记五(实践写自己的类装载器)
java基础核心学习笔记
java之jvm学习笔记十而(访问控制器的栈校验机制)
Java面试JVM+多线程重点突破.zip