`
greemranqq
  • 浏览: 966193 次
  • 性别: Icon_minigender_1
  • 来自: 重庆
社区版块
存档分类
最新评论
阅读更多

 一、引言:

        JAVA 是一个多线程并发的语言,现在只要有点经验的JAVA程序员,对于多线程、并发等词汇相信并不陌生,但是对于具体的运行原理,很多也都没深入,这里我也分享一部分自己的经验,主要对于线程安全以及锁的一些机制原理,进行介绍。关于线程的基本知识点,前面也说过了,可以了解一下。

 

1.1 什么是线程安全?

      这里我借“JAVA 并发实践”里面的话:当多个线程访问一个对象,如果不考虑这些线程在运行时环境下的调度和交替执行,也不需要额外的同步,或者调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那么这个对象是线程安全的。

      我的理解:多线程访问一个对象,任何情况下,都能保持正确行为,就是对象就是安全的。

      线程安全有强弱划分,分为5类:

      a.不可变

      不可变的对象的,也就是被声明成fianl的对象,只要被正确构建出来,在不发现this逃逸的情况下,其外部状态永远不会改变,永远不会看到多个线程中处于不一致的状态。也就是说所有对象的共享变量都声明成final ,那么就是安全的。

     

       b.绝对线程安全

        在某些情况下,我们希望我们的程序能在任何情况下都是安全的,比如加了final 类型的基本类型变量,这里可以认为是的,但是这种不可变的变量没有太大意义。而像StringBulider 类似的变量,即使加了final 类型,也不能认为是线程绝对安全,final 只能保证地址值不动。

   

       c.线程相对安全

         这里的相对安全比如我们了解的vector,StirngBuffer 等线程安全的类,也许vector 类的所有操作我们都加上内部锁,但是在使用过程中比如:声明一个 vector 的变量,然后A,B 线程并发操作它。假设A线程在增加元素,B线程在遍历获取元素,那么就会出现错误(元素个数不对),因此线程安全性更多的表现为对同一操作的正确执行安全,也是相对的安全。

 

       d.线程兼容

          简单的说就是,这个类本人不是线程安全的,但那时可以使用一些为外部手段,使其完成我们的线程安全。比如ArrayList,HashMap 本身不是线程安全的,但是如果你使用Collections.synchronizedList(Map)就可以达到安全效果,其实现原理很简单,就是对List 或者Map 进行封转,对其主要方法都加上内部锁,相当于集成一个List(Map),全部重写方法加上锁,调用父类执行体。具体的这里不深究。

 

      e.线程对立

        简单的说无论我们是否采用了线程安全的机制(比如加锁),或者其他同步措施,都不能保证多线程并发是安全的。比如Thread 的supend()和resume()方法,一个线程去中断线程,另一个线程去恢复线程。那么并发就容易产生死锁,这里两个方法也就废弃了。其他例子暂时不举了。

 

二、原子性操作

       当我们决定完成一个任务,通常情况下,在计算机中,看似很简单的任务也是有多个不同的步骤共同完成。该步骤是由cpu 的 一些指令完成的。比如我们常见的 i ++ ;这是一个非原子性操作,因为它先.从内存取出i的值,然后再增1,最后再写入内存中,经过三个步骤完成,如果在中间一个步骤被其他线程影响了,那么就可能出现错误。

      举个实际例子:我想完成过安检的过程,我会先取下包,然后放在检验机上,我走过去,然后等待通过检查,最后拿回来。但是实际过程发现有小偷在我将包放到检测机上,还没有进入检查过程中,被拿走了,然后我走过去,发现我的包没过来...这个悲剧的问题就发生了!

      那么如何完成原子性操作呢?

 

三、锁

       3.1 互斥同步

       互斥同步是我们最基本的保障并发安全的一种手段,比如刚才的例子,假设我通过安检这个过程,是不允许其他人接触或者靠近的,有一道独立的空间,也就是说我去通过检查的的行为和小偷接近我,偷我包的行为是互斥的,那么我的行为就很安全的完成了。

       互斥最简单的手段是synchronized 关键字,synchronized 关键字在通过编译之后,会在同步块前后分别形成monitorentor 和 monitorexit 两个字节码指令,这个两个指令都需要一个reference 类型来指明要锁定和解锁的对象,如果synchnronized 明确指定了对象参数,那就是这个对象的reference ,如果没有指明,那么就根据synchronied 修饰的是实例方法还是类方法,然后取对应对象的实例或者Class对象那个作为锁对象。

 

     3.2  synchronizd 工作原理

            Java 线程在执行到synchronied 的时候,会形成两个字节码指令,这里相当于是一个监视器(monitor),监控synchronized 保护的区域,监视器会设置几种状态用来区分请求线程:

             Contention List : 所有请求的线程将被首先放置到该竞争队列

             Entry List: Contention List 的那些有资格成为候选人的线程会被移到Entry List

             Wait Set:那些调用wait 方法被阻塞的线程被放置到这里

             OnDeck :任何时刻最多有一个线程正竞争锁,该线程称为OnDeck

             Owner :获得所的线程叫Owner

             !Owner :释放锁的线程

             下面是状态的转换关系:

             

 

我们知道,并发会引起竞争,那么上图更详细的描述了整个过程,我这里以我和小明和小强一起去上飞机为例子,假设所有通道唯一。

1.我们一起打车来到飞机场,相当于进入了Contention List 

2.然后我们准备去买票柜台(Entry List),但是还没到

3.这是小明发现身份证没带,打电话叫他妈妈送过来,他就只能等待,进入(WaitSet).

4.然后我和小明一起到柜台,如果柜台没有人,那么我们就去(Entry List) 买票。

5.这时候到我和小强一起跑到柜台,但是谁先买,得看OnDesk 的,相当于选择权在她手里,这里的竞争机制    是随机的,也就是说OnDesk 看谁顺眼,谁就能买。(当然大家现实都很文明排队~.~)

6.假设我得到的优先权,那么我就是Owner,只有我买票成功了,才有资格说OK。因为OnDesk 必然会问还 

   还有什么需要帮助的吗?这时候的决定权就在我手里了,然后我会!Owner,然后OnDesk 会以同样的方式    进行下一个人。

7.如果小明的票拿到了(唤醒),那么他也可以去柜台。

8.当然即使Owner 的线程,也可能出现问题,比如买票过程中 - -发现没钱了,等别人给我带,也只能进入        WaitSet 中了。

 

  3.2 重入锁

       synchronized 内部锁是互斥锁,也就是说当A线程请求B线程所占有的一个锁时,只能等待(阻塞),直到B释放它,如果B不释放,那么A就一直等待(阻塞)。也就是同一时间只能由同一线程进入synchronized 的保护块,这能保证它的原子性操作。

       但是相同持有该锁的线程可以再次进入该代码块,它再次请求获得锁的时候,会成功。这里的实现是当线程获得锁的时候,监视器(JVM) 会记录锁的占有者,并且与锁关联的计数器 + 1,当计数器为 0的时候我们才认为该锁没有被占用。

       

	class Parent{
		public synchronized void doSome(){}
	}
	class Child extends Parent{
		public synchronized void doSome(){
			// 如果没有重入锁 ,这里会出现死锁
			super.doSome();
		}
	}

 

 

    3.3 ReentrantLock 

     这个在java.util.concurrent(J.U.C) 下的的显示锁,也具有重入锁的特征,与synchronizd 相比,Lock 锁更加的灵活,因为内部锁synchronized 在阻塞的时候,其他线程必须等待,如果出点问题,可能无限等待下去,而且内部锁机制在状态转换过程中,需要映射到操作系统的原生线程上,这块转换比较耗时的,虽然JVM 也做了一些比如自旋锁的优化,但是还是不够。而Lock 锁,是表现在API 层次的锁,增加了额外的几个功能:

     a. 等待可中断:如果获得锁的线程,长时间不释放锁,正在等待的线程可以选择放弃等待,改为初期其他事情,这样不至于大家都等在那里,浪费时间。

     b.公平锁:当多个线程等待同一个锁时,必须按照申请锁的时间顺序来一次获得锁,可以通过boolean 类型的构造函数使用公平锁。当然此方法吞吐量稍微慢点,并且和线程优先级一样,仅仅是让先申请的的获得更大的大的机会,并不能完全保证它一定是公平的。

     c.绑定多个条件:ReentrantLock  对象可以同时绑定多个Condition 对象,而synchronized 中,锁对象的wait() 和 notify() 或notifyAll() 方法可以实现一个隐含的条件,如果多余一个条件关联的时候,就不得不额外加个锁,而ReentantLock 无需这么做,只需要多次newCondition 方法即可。

         这里简单的理解是:synchronized 阻塞,相当于大家不认识,由工作人员(CPU)调度,自由竞争锁,也不管竞争的人(线程)有啥意外情况。condition 相当于把大家都信息都获取了,比如A(线程) 获得买票(获得锁),结果发现没钱,他可以设置一个条件condition-A 等待,然后让出位置,让另外的人买。假设B(线程)买好票了,发现A有钱了,他可以通过condition-A 唤醒A,让他继续参与买票。相当于大家更和谐,不用一个卡死在前面,后面的人就一直等待,condition 可以多个条件切换工作。这里是通过一个队列进行的,至于具体的实现原理,可以参考:http://ifeve.com/understand-condition/ ,我们以后详细讲解。

          

 

小结:

        1.上面内容我是从深入理解JVM  和 并发实践 等地方copy 的,加入了自己的一些理解,分享

        2.由于都是理论性的东西,因此先介绍一小部分,不至于大家看着很累,但是希望看的时候能融入自己的理解,不然都是天书,没意思。

        3.关于其他锁机制原理等内容,以后慢慢分享吧,等我消化消化

        4.如果发现不理解,或者我理解错误的,请指出,以免误导他人嘛,非常感谢!

        

        

  • 大小: 23.5 KB
0
2
分享到:
评论
2 楼 greemranqq 2014-03-13  
xugangqiang 写道
讲的很好,特别是关于sync关键字的, monitorEnter, monitorExit
相当于这里有一道内存屏障,其他线程无法对这块内存进行访问,
这样就保证了这块内存的访问只有一个线程在进行,从而保证了安全性

以前一直不理解什么是内存屏障,这会有点理解了。
楼主最好再review一下文章,看看有没有错别字。

xugangqiang 写道
讲的很好,特别是关于sync关键字的, monitorEnter, monitorExit
相当于这里有一道内存屏障,其他线程无法对这块内存进行访问,
这样就保证了这块内存的访问只有一个线程在进行,从而保证了安全性

以前一直不理解什么是内存屏障,这会有点理解了。
楼主最好再review一下文章,看看有没有错别字。


额,纠结的错别是,好多年了。对了 我对锁的理解,以及内存屏障这块是有区别的,锁是监视器指令,内存屏障是另外一种,当然我没进行深入研究,这篇博文你可以参考,我们共同学习。
http://www.infoq.com/cn/articles/memory_barriers_jvm_concurrency
1 楼 xugangqiang 2014-03-13  
讲的很好,特别是关于sync关键字的, monitorEnter, monitorExit
相当于这里有一道内存屏障,其他线程无法对这块内存进行访问,
这样就保证了这块内存的访问只有一个线程在进行,从而保证了安全性

以前一直不理解什么是内存屏障,这会有点理解了。
楼主最好再review一下文章,看看有没有错别字。

相关推荐

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

    @youcanyouup。Tags:线程安全原子锁读写锁。

    CVI 线程锁、线程安全变量实例

    该文件中实例说明了如何在Labwindows/cvi中使用线程锁和线程安全变量进行多线程程序设计

    Java多线程-避免同步机制带来的死锁问题及用Lock锁解决线程安全问题

    Java多线程--避免同步机制带来的死锁问题及用Lock锁解决线程安全问题

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

    众所周知, 在多线程中,因为共享全局变量,会导致资源修改结果不一致,所以需要加锁来解决这个问题,保证同一时间只有一个线程对资源进行操作 但是在分布式架构中,我们的服务可能会有n个实例,但线程锁只对同一个...

    c#多线程读写锁操作

    c#怎么利用读写锁多线程高效安全的进行资源访问,提高程序性能

    java多线程安全性基础介绍.pptx

    java多线程安全性基础介绍 线程安全 正确性 什么是线程安全性 原子性 竞态条件 i++ 读i ++ 值写回i 可见性 JMM 由于cpu和内存加载速度的差距,在两者之间增加了多级缓存导致,内存并不能直接对cpu可见。 ...

    Qt两种方法实现多线程并安全结束线程及QMutex加锁Qt单例化实现

    Qt两种方法实现多线程的开启,及子线程的安全结束线程,及QMutex加锁,Qt单例化实现

    C#多线程读写sqlite

    多线程读写sqlite数据库,同步锁,计时测试读写性能,

    .Net 多线程详解

    .doc 格式 详细解析多线程技术 基础篇 • 怎样创建一个线程 • 受托管的线程与 Windows线程 • 前台线程与后台线程 • 名为BeginXXX和EndXXX的方法是做什么用的 • 异步和多线程有什么关联 WinForm多线程编程...

    Java、Android线程安全与多线程数据同步

    NoHttp核心架构之多线程通信、线程安全、线程同步;synchronized锁,Lock锁;具体讲解请移步:http://blog.csdn.net/yanzhenjie1003/article/details/50992468

    Java多线程安全问题和锁

    多线程安全问题和锁 文章目录多线程安全问题和锁线程在jvm中的特点锁的出现synchronized 关键字-监视器锁monitor lock死锁的产生和避免 什么是线程安全问题? 当多个线程同时操作同一个数据是,可能会出现数据不一样...

    C#线程锁介绍源码

    这 条是最根本的东西,开发完全线程安全的程序是件很费时费力的事情,在电子商务等涉及金融系统的案例中,许多逻辑都必须严格的线程安全,所以我们不得不牺牲 一些性能,和很多的开发时间来做这方面的工作。...

    Java、Android多线程、线程安全、线程同步

    NoHttp核心架构之多线程通信、线程安全、线程同步;synchronized锁,Lock锁;具体讲解请移步博客:http://blog.csdn.net/yanzhenjie1003/article/details/50992468

    计算机后端-Java-Java核心基础-第20章 多线程 13. Lock锁方式解决线程安全问题.avi

    计算机后端-Java-Java核心基础-第20章 多线程 13. Lock锁方式解决线程安全问题.avi

    java 多线程设计模式 进程详解

    《JAVA多线程设计模式》PDF 下载 《Java线程 高清晰中文第二版》中文第二版(PDF) 前言 第一章 线程简介 Java术语 线程概述 为什么要使用线程? 总结 第二章 Java线程API 通过Thread类创建线程 使用Runable接口...

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

    在多线程并行的情况下会出现共享数据会线程间读取与写入不一直的情况,为了解决这种情况,通常会使用锁来解决,也就是将并行改为串行。但是在使用穿行违背了使用多线程并发的初衷,这种情况下我们可以考虑采用线程...

    【Java正来-深入理解JVM】线程安全与优化。xmind思维导图

    线程安全与锁优化:当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者再调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果。

    Linux c++多线程串口编程demo

    使用多线程进行串口编程,获取串口数据,利用互斥锁和信号量在不同的线程中安全地操作数据,希望该demo能帮助你快速理解并掌握上述知识。

    C# 多线程教材

    锁和线程安全 Interrupt 和 Abort 线程状态 等待句柄 同步环境 使用多线程 单元模式和Windows Forms BackgroundWorker类 ReaderWriterLock类 线程池 异步委托 计时器 局部储存 高级话题 非阻止同步 Wait和Pulse ...

    linux c 多线程安全日志系统

    内涵头文件以及一个程序实现,内涵测试程序。linux c实现,使用线程锁,信号量,文件操作,sync等技术。测试:进入build目录 cmake 后make

Global site tag (gtag.js) - Google Analytics