`
purelilium
  • 浏览: 4653 次
  • 性别: 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局部变量线程安全原理分析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

    c_safe_lib:c数据结构线程安全库

    c_safe_lib c线程安全库,这个项目的目的是创造一套可以工程使用的c数据结构库,在实际工程中一套优秀高效的数据结构库是必不可少的。主要解决俩个方面:1.线程安全2.跨平台 linux 、windows实现原理功能状态实现原理...

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

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

    C#线程安全的事件类研究报告

    C#线程安全的事件类研究报告,附有详细源码以及分析批注,内容翔实,帮助.NET编程人员进一步深入理解.NET框架中的事件工作原理和不足。

    Java多线程与线程安全实践-基于Http协议的断点续传(源码)

    通过本毕业设计题目的研究和实践,学生可以深入理解Java多线程技术和线程安全机制,掌握Http协议的基本原理和应用方法,提高软件设计和开发能力。同时,学生还可以了解断点续传技术在实际应用中的重要性,以及如何...

    线程安全及Python中的GIL原理分析

    本文讲述了线程安全及Python中的GIL。分享给大家供大家参考,具体如下: 摘要 什么是线程安全? 为什么python会使用GIL的机制? 在多核时代的到来的背景下,基于多线程来充分利用硬件的编程方法也不断发展起来, 但是...

    使用redis分布式锁解决并发线程资源共享问题

    原理 修改某个资源时, 在redis中设置一个key,value根据实际情况自行决定如何表示 我们既然要通过检查key是否存在(存在表示有线程在修改资源,资源上锁,其他线程不可同时操作,若key不存在,表示资源未被线程占用...

    Java实现的多线程与线程安全实践-基于Http协议的断点续传,可用作毕业设计,课程设计

    毕业论文——包含了一篇探讨多线程与线程安全实践项目背景、技术原理和实现过程的学术论文,可作为论文撰写参考。通过阅读论文,学习者可以了解项目的理论基础,深入理解项目的实现细节,提升分析和解决问题的能力。...

    基于java线程安全问题及原理性分析

    下面小编就为大家带来一篇基于java线程安全问题及原理性分析。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧

    java多线程自增效率比较及原理解析

    在多线程环境下,对于自增操作需要考虑线程安全问题,常见的解决方法包括使用synchronized关键字、AtomicInteger、LongAdder和LongAccumulator等。本文给出了使用这些方法实现自增的代码演示,并通过多线程测试比较...

    《Windows安全原理与技术》简介与教学大纲

    《Windows安全原理与技术》是信息安全专业网络安全方向专业选修(限选)课程。本课程的目的在于使学生掌握Windows系统内核的基本原理和Windows安全技术。本课程内容: WINDOWS系统的内存管理,虚拟内存访问,文件的...

    Java多线程与线程安全实践-基于Http协议的断点续传

    基于Http协议的断点续传是Java多线程和线程安全的一个典型应用。下面详细解说一下这个实践的实现过程: 理解Http协议 在实现断点续传之前,我们需要先理解Http协议的基本工作原理。Http协议是一个基于请求-响应模式...

    python3 多线程

    多线程学习,通过两个函数来测试多线程的运行原理,运行时可以反复看时间的先后

    java static方法 引用static属性 存在线程安全问题-原理分析

    java static方法 引用static属性 存在线程安全问题-原理分析 测试代码 package javabase.src.thread; public class StaticMethodTest { private static int a = 0; public static void main(String[] args) { new...

    java集合类原理面试题

    线程安全和线程不安全的分别有哪些? Map接口有哪些实现类? 描述一下Map put的过程 如何得到一个线程安全的Map? HashMap有什么特点? ConcurrentHashMap是怎么分段分组的? ConcurrentHashMap是怎么分段分组的? ...

    Java多线程和并发知识整理

    1.5线程安全的分类 1.6线程安全的方法 二、线程基础 2.1状态 2.2使用方式 2.3基础机制 2.4中断 2.5互斥同步 2.6线程合作 三、Synchronized 详解 3.1 使用 3.2 原理分析 3.3 JVM中锁的优化 3.4 ...

    Java SimpleDateFormat线程安全问题原理详解

    主要介绍了Java SimpleDateFormat线程安全问题原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下

    DLL注入之远线程方式

    远线程注入的基本原理就是通过在另一个进程中创建远程线程的方法进入目标进程的内存地址空间。使用插入到目标进程中的远程线程将该DLL插入到目标进程的地址空间,即利用该线程通过调用Windows API LoadLibrary函数...

Global site tag (gtag.js) - Google Analytics