`
purelilium
  • 浏览: 4883 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

线程安全原理

 
阅读更多

原理:

首先要明白线程的工作原理,jvm有一个main memory,而每个线程有自己的working memory,一个线程对一个variable进行操作时,都要在自己的working memory里面建立一个copy,操作完之后再写入main memory。多个线程同时操作同一个variable,就可能会出现不可预知的结果。根据上面的解释,很容易想出相应的scenario。
而用synchronized的关键是建立一个monitor,这个monitor可以是要修改的variable也可以其他你认为合适的object比如method,然后通过给这个monitor加锁来实现线程安全,每个线程在获得这个锁之后,要执行完 load到workingmemory -> use&assign -> store到mainmemory 的过程,才会释放它得到的锁。这样就实现了所谓的线程安全。

举例 :

比如一个 ArrayList 类,在添加一个元素的时候,它可能会有两步来完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。

在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;
而如果是在多线程情况下,比如有两个线程,线程 A 先将元素存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B也向此 ArrayList 添加元素,因为此时 Size 仍然等于 0 (注意哦,我们假设的是添加一个元素是要两个步骤哦,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值。
那好,现在我们来看看 ArrayList 的情况,元素实际上只有一个,存放在位置 0,而 Size 却等于 2。这就是“线程不安全”了。

Java线程安全系列(1)--Servlet线程安全

关键字: servlet 线程安全


概述
在探讨java线程安全前,让我们先简要介绍一下Java语言。

任何语言,如C++,C#,Java,它们都有相通之处,特别是语法,但如果有人问你,Java语言的核心是什么?类库?关键字?语法?似乎都不是。Java语言的核心,也就是Sun始终不愿意开源的东西:Java虚拟机的实现(不过sun公开了其Java虚拟机规范),也就有了BEA的JRockit,IBM的Jikes,Sun的Hotspot。

Java的核心有两点,Java类加载(Java Class Loader)和Java内存管理,它们具体体现在Java类库的以下几个类:

java.lang.ClassLoader(java.lang.Class):我们调用的类,包括其接口和超类,import的类是怎么被Java虚拟机载入的?为什么static的字段在servlet容器里面可以一直生存下去(Spring容器中)?

java.lang.Thread(java.lang.ThreadLocal):垃圾回收是怎么进行的(垃圾回收线程)?我们的程序是怎么退出的?

java.lang.refelect.Proxy(java.lang.refelect.Method):为什么Tomcat、Tapestry、Webwork、Spring等容器和框架可以通过配置文件来调用我们写的类?Servlet规范、JSF规范、EJB规范、JDBC规范究竟是怎么回事?为什么它们几乎都是一些接口,而不是具体类?


Servlet线程安全

在Java的server side开发过程中,线程安全(Thread Safe)是一个尤为突出的问题。因为容器,如Servlet、EJB等一般都是多线程运行的。虽然在开发过程中,我们一般不考虑这些问题,但诊断问题(Robust),程序优化(Performance),我们必须深入它们。

什么是线程安全?

引用
Thread-safe describes a program portion or routine that can be called from multiple programming threads without unwanted interaction between the threads。


在Java里,线程安全一般体现在两个方面:

1、多个thread对同一个java实例的访问(read和modify)不会相互干扰,它主要体现在关键字synchronized。如ArrayList和Vector,HashMap和Hashtable(后者每个方法前都有synchronized关键字)。如果你在interator一个List对象时,其它线程remove一个element,问题就出现了。

2、每个线程都有自己的字段,而不会在多个线程之间共享。它主要体现在java.lang.ThreadLocal类,而没有Java关键字支持,如像static、transient那样。

一个普遍的疑问,我们的Servlet中能够像JavaBean那样declare instance或static字段吗?如果不可以?会引发什么问题?

答案是:不可以。我们下面以实例讲解:

首先,我们写一个普通的Servlet,里面有instance字段count:


Java代码复制代码
  1. publicclassSimpleServletextendsHttpServlet
  2. {
  3. //AvariablethatisNOTthread-safe!
  4. privateintcounter=0;
  5. publicvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException
  6. {
  7. doPost(req,resp);
  8. }
  9. publicvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException
  10. {
  11. resp.getWriter().println("<HTML><BODY>");
  12. resp.getWriter().println(this+"==>");
  13. resp.getWriter().println(Thread.currentThread()+":<br>");
  14. for(intc=0;c<10;c++)
  15. {
  16. resp.getWriter().println("Counter="+counter+"<BR>");
  17. try
  18. {
  19. Thread.sleep((long)Math.random()*1000);
  20. counter++;
  21. }
  22. catch(InterruptedExceptionexc)
  23. {
  24. }
  25. }
  26. resp.getWriter().println("</BODY></HTML>");
  27. }
  28. }
  1. publicclassSimpleServletextendsHttpServlet
  2. {
  3. //AvariablethatisNOTthread-safe!
  4. privateintcounter=0;
  5. publicvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException
  6. {
  7. doPost(req,resp);
  8. }
  9. publicvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException
  10. {
  11. resp.getWriter().println("<HTML><BODY>");
  12. resp.getWriter().println(this+"==>");
  13. resp.getWriter().println(Thread.currentThread()+":<br>");
  14. for(intc=0;c<10;c++)
  15. {
  16. resp.getWriter().println("Counter="+counter+"<BR>");
  17. try
  18. {
  19. Thread.sleep((long)Math.random()*1000);
  20. counter++;
  21. }
  22. catch(InterruptedExceptionexc)
  23. {
  24. }
  25. }
  26. resp.getWriter().println("</BODY></HTML>");
  27. }
  28. }


然后,我们通过一个html页面向该servlet发出三次请求:

Java代码复制代码
  1. <HTML>
  2. <BODY>
  3. <TABLE>
  4. <TR>
  5. <TD><IFRAMEsrc="./SimpleServlet"name="servlet1"height="200%"></IFRAME></TD>
  6. </TR>
  7. <TR>
  8. <TD><IFRAMEsrc="./SimpleServlet"name="servlet2"height="200%"></IFRAME></TD>
  9. </TR>
  10. <TR>
  11. <TD><IFRAMEsrc="./SimpleServlet"name="servlet3"height="200%"></IFRAME></TD>
  12. </TR>
  13. </TABLE>
  14. </BODY>
  15. </HTML>
  1. <HTML>
  2. <BODY>
  3. <TABLE>
  4. <TR>
  5. <TD><IFRAMEsrc="./SimpleServlet"name="servlet1"height="200%"></IFRAME></TD>
  6. </TR>
  7. <TR>
  8. <TD><IFRAMEsrc="./SimpleServlet"name="servlet2"height="200%"></IFRAME></TD>
  9. </TR>
  10. <TR>
  11. <TD><IFRAMEsrc="./SimpleServlet"name="servlet3"height="200%"></IFRAME></TD>
  12. </TR>
  13. </TABLE>
  14. </BODY>
  15. </HTML>




刷新页面几次后,产生的结果为:

com.zwchen.servlet.SimpleServlet@11e1bbf ==> Thread[http-8081-Processor23,5,main]:
Counter = 60
Counter = 61
Counter = 62
Counter = 65
Counter = 68
Counter = 71
Counter = 74
Counter = 77
Counter = 80
Counter = 83



com.zwchen.servlet.SimpleServlet@11e1bbf ==> Thread[http-8081-Processor22,5,main]:
Counter = 61
Counter = 63
Counter = 66
Counter = 69
Counter = 72
Counter = 75
Counter = 78
Counter = 81
Counter = 84
Counter = 87



com.zwchen.servlet.SimpleServlet@11e1bbf ==> Thread[http-8081-Processor24,5,main]:
Counter = 61
Counter = 64
Counter = 67
Counter = 70
Counter = 73
Counter = 76
Counter = 79
Counter = 82
Counter = 85
Counter = 88

我们会发现三点:

servlet只产生了一个Servlet对象,因为输出this时,其hashcode都一样,

servlet在不同的线程(线程池)中运行,如http-8081-Processor22,http-8081-Processor23

Count被这三个doGet方法共享,并且并行修改。



上面的结果,违反了线程安全的两个方面。

那么,我们怎样保证按照我们期望的结果运行呢?首先,我想保证产生的count都是顺序执行的。

我们将Servlet代码重构如下:



Java代码复制代码
  1. publicclassSimpleServletextendsHttpServlet
  2. {
  3. //AvariablethatisNOTthread-safe!
  4. privateintcounter=0;
  5. privateStringmutex="";
  6. publicvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)
  7. throwsServletException,IOException
  8. {
  9. doPost(req,resp);
  10. }
  11. publicvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)
  12. throwsServletException,IOException
  13. {
  14. resp.getWriter().println("<HTML><BODY>");
  15. resp.getWriter().println(this+":<br>");
  16. synchronized(mutex)
  17. {
  18. for(intc=0;c<10;c++)
  19. {
  20. resp.getWriter().println("Counter="+counter+"<BR>");
  21. try
  22. {
  23. Thread.sleep((long)Math.random()*1000);
  24. counter++;
  25. }
  26. catch(InterruptedExceptionexc){}
  27. }
  28. }
  29. resp.getWriter().println("</BODY></HTML>");
  30. }
  31. }
  1. publicclassSimpleServletextendsHttpServlet
  2. {
  3. //AvariablethatisNOTthread-safe!
  4. privateintcounter=0;
  5. privateStringmutex="";
  6. publicvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)
  7. throwsServletException,IOException
  8. {
  9. doPost(req,resp);
  10. }
  11. publicvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)
  12. throwsServletException,IOException
  13. {
  14. resp.getWriter().println("<HTML><BODY>");
  15. resp.getWriter().println(this+":<br>");
  16. synchronized(mutex)
  17. {
  18. for(intc=0;c<10;c++)
  19. {
  20. resp.getWriter().println("Counter="+counter+"<BR>");
  21. try
  22. {
  23. Thread.sleep((long)Math.random()*1000);
  24. counter++;
  25. }
  26. catch(InterruptedExceptionexc){}
  27. }
  28. }
  29. resp.getWriter().println("</BODY></HTML>");
  30. }
  31. }



我们的输出结果为:

com.zwchen.servlet.SimpleServlet@109da93:
Counter = 0
Counter = 1
Counter = 2
Counter = 3
Counter = 4
Counter = 5
Counter = 6
Counter = 7
Counter = 8
Counter = 9


com.zwchen.servlet.SimpleServlet@109da93:
Counter = 10
Counter = 11
Counter = 12
Counter = 13
Counter = 14
Counter = 15
Counter = 16
Counter = 17
Counter = 18
Counter = 19

com.zwchen.servlet.SimpleServlet@109da93:
Counter = 20
Counter = 21
Counter = 22
Counter = 23
Counter = 24
Counter = 25
Counter = 26
Counter = 27
Counter = 28
Counter = 29


这符合了我们的要求,输出都是按顺序的,这正式synchronized的含义。

附带说一下,我现在synchronized的是一个字符串变量mutex,不是this对象,这主要是从performance和Scalability考虑。Synchronized用在this对象上,会带来严重的可伸缩性的问题(Scalability),所有的并发请求都要排队!
分享到:
评论

相关推荐

    c# 线程安全队列的用法原理及使用示例

    什么是线程安全? 答:线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等...

    Java局部变量线程安全原理分析

    Java局部变量线程安全原理分析 Java局部变量线程安全原理分析是Java编程语言中非常重要的一个概念。通过对Java局部变量线程安全原理的分析,我们可以更好地理解Java的线程安全机制,避免在编程中出现线程安全问题。...

    windows环境下的多线程编程原理与应用

    线程安全意味着函数或类在多线程环境中能正确工作,不出现竞态条件或死锁。这通常需要使用同步机制来保护共享数据。 六、线程池 线程池是一种管理线程的机制,它预先创建一组线程,当有任务需要执行时,从池中获取...

    线程安全你还在用synchronized?

    ### 线程安全与ThreadLocal的深入剖析 #### 一、引言 在多线程编程中,确保数据的一致性和线程安全性是至关重要的。传统的锁机制,如`synchronized`关键字,虽然能有效避免多线程环境下的数据竞争问题,但其使用...

    servlet与Struts action线程安全问题分析

    开发者需要理解框架的工作原理,并结合最佳实践来避免线程安全问题。 总之,理解和处理Servlet和Struts Action的线程安全问题对于构建高性能、可靠的Java Web应用至关重要。通过避免使用实例变量、采用同步控制、...

    易语言线程安全之原子锁与读写锁

    在IT行业中,线程安全是多线程编程中的一个重要概念,确保多个线程并发执行时,数据的正确性和完整性不会受到影响。线程安全通常通过同步机制来实现,其中包括原子操作和锁机制。本文将深入探讨易语言中的原子锁与...

    线程安全测试类

    通过对`ThreadTest`类的分析和测试,我们可以深入了解`synchronized`关键字的工作原理,以及它如何影响线程安全和执行顺序。这对于我们设计和优化高并发的Java应用至关重要,尤其是在分布式系统和微服务架构中,理解...

    Action是否线程安全

    在Java编程中,"Action"通常指的是`...对于`javax.swing.ActionEvent`,理解Swing的事件模型和EDT的工作原理是保证线程安全的关键。而对于自定义的`Action`接口,开发者需要根据具体需求来设计和实现线程安全的代码。

    windows环境下多线程编程原理

    在实际应用中,多线程编程需要注意线程安全问题,比如数据竞争和死锁。数据竞争发生在多个线程同时修改同一份数据时,可能导致不可预测的结果。为避免这种情况,可以使用互斥量(mutex)、信号量(semaphore)或临界...

    射频卡多线程读写原理及其实现

    ### 射频卡多线程读写原理及其实现 #### 摘要 本文深入探讨了射频卡(RFID)的工作原理及其基于多线程技术的读写实现方式。射频卡作为一种非接触式的智能卡技术,在现代社会中得到了广泛应用。通过分析射频卡的基本...

    线程的工作原理

    ### 线程的工作原理详解 #### 一、线程的基本概念 线程是操作系统能够进行运算...线程作为一种轻量级进程,能够有效提高程序的性能和响应能力,但也需要开发者关注线程安全问题,确保多线程应用的正确性和稳定性。

    线程安全型队列的实现

    在IT领域,线程安全是多线程编程中的一个重要概念,指的是当多个线程访问同一块资源时,代码能够正确地处理并发访问,...在进行课程设计时,理解这些组件的工作原理以及它们如何协同工作以确保线程安全是至关重要的。

    Android中多线程下载原理实现案例

    线程间同步和更新进度是非常关键的,避免出现数据竞争问题,可以使用`synchronized`关键字或者`Lock`对象来保证线程安全。 为了实现断点续传功能,我们需要在每个线程开始下载前检查本地是否有已下载的部分。如果...

    Windows环境下的多线程编程原理与应用.rar

    7. **线程安全**:在多线程环境中,必须确保对共享资源的操作是线程安全的,通常通过锁(如互斥锁)或者原子操作实现。 8. **线程退出和清理**:当线程完成工作后,会自动退出。可以通过`ExitThread`函数强制退出,...

    vs2003-hiredis-vc++客户端连接池,线程安全

    本项目"vs2003-hiredis-vc++客户端连接池,线程安全"正是针对这种情况设计的,旨在提供一个线程安全的Redis客户端连接池解决方案。 首先,我们需要了解`hiredis`。`hiredis`是由Antirez编写的轻量级、高性能的C语言...

    线程安全单例

    ### 线程安全单例 #### 背景与概念 在软件开发过程中,单例模式是一种常用的设计模式,其目的是确保一个类只有一个实例,并提供一个全局访问点。单例模式的应用非常广泛,比如数据库连接池管理、日志对象等场景。...

    线程安全的jdbc连接池

    线程安全的JDBC连接池就是为此目的设计的一种优化手段。在这个简单的实现中,我们利用了`ConcurrentLinkedQueue`数据结构来确保多线程环境下的安全性和效率。 首先,让我们了解什么是JDBC连接池。JDBC连接池(Java ...

    c#高效的线程安全队列ConcurrentQueueT的实现

    众所周知,在普通的非线程安全队列有两种实现方式: 1.使用数组实现的循环队列。 2.使用链表实现的队列。 先看看两种方式的优劣:  .Net Farmework中的普通队列Queue的实现使用了第一种方式,缺点是当队列空间不足会...

    jdk中线程安全的集合类.docx

    ### JDK中线程安全的集合类详解 #### 1. 引言 在Java开发过程中,随着并发编程的日益普及,对于数据结构的选择也提出了更高的要求。非线程安全的数据结构如`HashMap`在高并发场景下可能会出现数据不一致等问题,这...

    在多线程的原理基础上设计的多线程应用程序设计—监控软件

    2. **线程安全**:确保在多线程环境下,数据的读写操作不会导致错误或不一致的结果。在VB中,可以使用`Shared`关键字定义线程间共享的成员,或者使用`Interlocked`类进行原子操作。 3. **线程优先级**:VB允许设置...

Global site tag (gtag.js) - Google Analytics