首先要明白线程的工作原理,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 的值。
概述
在探讨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:
-
publicclassSimpleServletextendsHttpServlet
-
-
{
-
-
privateintcounter=0;
-
publicvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException
-
{
-
doPost(req,resp);
-
}
-
-
publicvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException
-
{
-
resp.getWriter().println("<HTML><BODY>");
-
-
resp.getWriter().println(this+"==>");
-
-
resp.getWriter().println(Thread.currentThread()+":<br>");
-
for(intc=0;c<10;c++)
-
{
-
resp.getWriter().println("Counter="+counter+"<BR>");
-
try
-
{
-
Thread.sleep((long)Math.random()*1000);
-
counter++;
-
}
-
catch(InterruptedExceptionexc)
-
{
-
}
-
}
-
resp.getWriter().println("</BODY></HTML>");
-
}
-
}
-
publicclassSimpleServletextendsHttpServlet
-
-
{
-
-
privateintcounter=0;
-
publicvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException
-
{
-
doPost(req,resp);
-
}
-
-
publicvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)throwsServletException,IOException
-
{
-
resp.getWriter().println("<HTML><BODY>");
-
-
resp.getWriter().println(this+"==>");
-
-
resp.getWriter().println(Thread.currentThread()+":<br>");
-
for(intc=0;c<10;c++)
-
{
-
resp.getWriter().println("Counter="+counter+"<BR>");
-
try
-
{
-
Thread.sleep((long)Math.random()*1000);
-
counter++;
-
}
-
catch(InterruptedExceptionexc)
-
{
-
}
-
}
-
resp.getWriter().println("</BODY></HTML>");
-
}
-
}
然后,我们通过一个html页面向该servlet发出三次请求:
-
<HTML>
-
-
<BODY>
-
-
<TABLE>
-
<TR>
-
<TD><IFRAMEsrc="./SimpleServlet"name="servlet1"height="200%"></IFRAME></TD>
-
</TR>
-
<TR>
-
<TD><IFRAMEsrc="./SimpleServlet"name="servlet2"height="200%"></IFRAME></TD>
-
</TR>
-
<TR>
-
<TD><IFRAMEsrc="./SimpleServlet"name="servlet3"height="200%"></IFRAME></TD>
-
</TR>
-
</TABLE>
-
</BODY>
-
</HTML>
-
<HTML>
-
-
<BODY>
-
-
<TABLE>
-
<TR>
-
<TD><IFRAMEsrc="./SimpleServlet"name="servlet1"height="200%"></IFRAME></TD>
-
</TR>
-
<TR>
-
<TD><IFRAMEsrc="./SimpleServlet"name="servlet2"height="200%"></IFRAME></TD>
-
</TR>
-
<TR>
-
<TD><IFRAMEsrc="./SimpleServlet"name="servlet3"height="200%"></IFRAME></TD>
-
</TR>
-
</TABLE>
-
</BODY>
-
</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代码重构如下:
-
publicclassSimpleServletextendsHttpServlet
-
-
{
-
-
-
-
privateintcounter=0;
-
-
privateStringmutex="";
-
-
-
-
publicvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)
-
-
throwsServletException,IOException
-
{
-
-
doPost(req,resp);
-
-
}
-
-
publicvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)
-
-
throwsServletException,IOException
-
-
{
-
-
resp.getWriter().println("<HTML><BODY>");
-
-
resp.getWriter().println(this+":<br>");
-
-
synchronized(mutex)
-
-
{
-
for(intc=0;c<10;c++)
-
{
-
resp.getWriter().println("Counter="+counter+"<BR>");
-
try
-
{
-
-
Thread.sleep((long)Math.random()*1000);
-
counter++;
-
}
-
catch(InterruptedExceptionexc){}
-
}
-
}
-
resp.getWriter().println("</BODY></HTML>");
-
}
-
-
}
-
publicclassSimpleServletextendsHttpServlet
-
-
{
-
-
-
-
privateintcounter=0;
-
-
privateStringmutex="";
-
-
-
-
publicvoiddoGet(HttpServletRequestreq,HttpServletResponseresp)
-
-
throwsServletException,IOException
-
{
-
-
doPost(req,resp);
-
-
}
-
-
publicvoiddoPost(HttpServletRequestreq,HttpServletResponseresp)
-
-
throwsServletException,IOException
-
-
{
-
-
resp.getWriter().println("<HTML><BODY>");
-
-
resp.getWriter().println(this+":<br>");
-
-
synchronized(mutex)
-
-
{
-
for(intc=0;c<10;c++)
-
{
-
resp.getWriter().println("Counter="+counter+"<BR>");
-
try
-
{
-
-
Thread.sleep((long)Math.random()*1000);
-
counter++;
-
}
-
catch(InterruptedExceptionexc){}
-
}
-
}
-
resp.getWriter().println("</BODY></HTML>");
-
}
-
-
}
我们的输出结果为:
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),所有的并发请求都要排队!
相关推荐
什么是线程安全? 答:线程安全是多线程编程时的计算机程序代码中的一个概念。在拥有共享数据的多条线程并行执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等...
主要介绍了Java局部变量线程安全原理分析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
c_safe_lib c线程安全库,这个项目的目的是创造一套可以工程使用的c数据结构库,在实际工程中一套优秀高效的数据结构库是必不可少的。主要解决俩个方面:1.线程安全2.跨平台 linux 、windows实现原理功能状态实现原理...
众所周知,在普通的非线程安全队列有两种实现方式: 1.使用数组实现的循环队列。 2.使用链表实现的队列。 先看看两种方式的优劣: .Net Farmework中的普通队列Queue的实现使用了第一种方式,缺点是当队列空间不足会...
C#线程安全的事件类研究报告,附有详细源码以及分析批注,内容翔实,帮助.NET编程人员进一步深入理解.NET框架中的事件工作原理和不足。
通过本毕业设计题目的研究和实践,学生可以深入理解Java多线程技术和线程安全机制,掌握Http协议的基本原理和应用方法,提高软件设计和开发能力。同时,学生还可以了解断点续传技术在实际应用中的重要性,以及如何...
本文讲述了线程安全及Python中的GIL。分享给大家供大家参考,具体如下: 摘要 什么是线程安全? 为什么python会使用GIL的机制? 在多核时代的到来的背景下,基于多线程来充分利用硬件的编程方法也不断发展起来, 但是...
原理 修改某个资源时, 在redis中设置一个key,value根据实际情况自行决定如何表示 我们既然要通过检查key是否存在(存在表示有线程在修改资源,资源上锁,其他线程不可同时操作,若key不存在,表示资源未被线程占用...
毕业论文——包含了一篇探讨多线程与线程安全实践项目背景、技术原理和实现过程的学术论文,可作为论文撰写参考。通过阅读论文,学习者可以了解项目的理论基础,深入理解项目的实现细节,提升分析和解决问题的能力。...
下面小编就为大家带来一篇基于java线程安全问题及原理性分析。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
在多线程环境下,对于自增操作需要考虑线程安全问题,常见的解决方法包括使用synchronized关键字、AtomicInteger、LongAdder和LongAccumulator等。本文给出了使用这些方法实现自增的代码演示,并通过多线程测试比较...
《Windows安全原理与技术》是信息安全专业网络安全方向专业选修(限选)课程。本课程的目的在于使学生掌握Windows系统内核的基本原理和Windows安全技术。本课程内容: WINDOWS系统的内存管理,虚拟内存访问,文件的...
基于Http协议的断点续传是Java多线程和线程安全的一个典型应用。下面详细解说一下这个实践的实现过程: 理解Http协议 在实现断点续传之前,我们需要先理解Http协议的基本工作原理。Http协议是一个基于请求-响应模式...
多线程学习,通过两个函数来测试多线程的运行原理,运行时可以反复看时间的先后
java static方法 引用static属性 存在线程安全问题-原理分析 测试代码 package javabase.src.thread; public class StaticMethodTest { private static int a = 0; public static void main(String[] args) { new...
线程安全和线程不安全的分别有哪些? Map接口有哪些实现类? 描述一下Map put的过程 如何得到一个线程安全的Map? HashMap有什么特点? ConcurrentHashMap是怎么分段分组的? ConcurrentHashMap是怎么分段分组的? ...
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线程安全问题原理详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
远线程注入的基本原理就是通过在另一个进程中创建远程线程的方法进入目标进程的内存地址空间。使用插入到目标进程中的远程线程将该DLL插入到目标进程的地址空间,即利用该线程通过调用Windows API LoadLibrary函数...