关于JVM,并发的一些思考

来源:互联网 发布:笔记本网络共享给手机 编辑:程序博客网 时间:2024/05/22 03:05

     一直以来,我都是对并发的相关知识不理解,做为一个眼高手低的人,我之前看多线程只是觉得很酷,对于其原理等等都不了解,只是知道是用来干什么的,但是心中仍是十分疑惑,后来的结合JVM之后,对于JVM有了一些理解之后,发现很多东西都是理解了;

    首先,我们先明确一下什么是线程,程序执行流的最小执行单位,一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。线程ID当然我们知道了,是用来明确我们的线程以便于和其他线程区别的,指令指针,相当于我们的java代码编译之后执行汇编代码或者计算机的指令的指针,是的是指针,学习C语言的时候总是想绕过去的指针.

寄存器,是一个比硬盘和内存块的多的高速存贮部件。可以快速存贮一些执行和指针等等。堆栈这些东西就是内存当中的东西了。

    关于JVM的内存分配,这个网上有大量的关于的JVM内存分配的博客,参考http://www.cnblogs.com/dingyingsi/p/3760447.html,总得来说,需要强调栈区线程私有和堆区共有;

   

上图原地址为http://www.cnblogs.com/dingyingsi/p/3760447.html

     可是这个和多线程的关系在哪里呢?

     首先栈区因为线程私有,我们基本上不必考虑线程安全,但是到了堆区,这个时候线程安全是必须要考虑的问题了,也许我们大多数的时候写代码发现不需要考虑线程安全问题啊,因为大多数的时候我们实例化对象的时候都是new一个对象出来,线程每次执行代码到这个时候,我们new一个对象,从某种程度上来看是局部变量,再加之这个对象的引用是归栈区所有,这样可以维持线程安全,而且只要这个对象当中的属性占用内存很小,或者就是一个无属性的对象,那么该对象的方法是存在于方法区的,方法区本身就是用来保存类信息、常量、静态变量、即时编译器编译后的代码等数据这些信息的,该类的各个对象之间是共用的,而且保证了线程安全。所以这样我们可以再次缩小大部分时候我们需要对其进行线程安全的区域(还有些静态方法,和静态变量这里我们先暂时不考虑,以为静态变量一次运行处处使用不再更改的特性让这一问题变得简单)。

      

    说道线程安全,在java当中我首先想到的就是synchronized,这个关键字的用法分为几种,静态方法使用的和非静态方法中使用的。静态方法使用时锁住的是对象的类,好吧,这个有点拗口,例如对象是A,那么静态方法使用synchronized锁住的就是A.class,(还有顺便提一下,静态变量是不属于某个实例对象而是属性类,也称之为类变量,只要程序加载了类的字节码,不需要实例化对象,静态变量就被分配了内存空间,静态变量就可以使用了),那么非静态方法锁住的就是实例化的对象,更明确一点的是锁住的是该实例化对象的属性,方法编译之后的字节码文件是方法区的,类公用的,对象是由属性和方法组成的。所以现在知道我们的synchronized锁住的区域是内存当中的那一片区域了吧,具体的实现是synchronized会在对象头上面做上了改变,还有就是操作系统层面上面的,markword很重要在GC的时候也要利用对象头。

    既然已经说完了synchronized,那么我们看这个关键字做的不错,保证了线程安全,但是他降低了效率,是的线程安全从当前技术上面来看,如果同样的技术做到线程安全,我们一定会在效率上面有所损失。

 

上图t-34坦克

http://pic.baike.soso.com/p/20140421/20140421130109-109124026.jpg

上图虎式坦克

       如果要兼顾一件事情,你一定会失去什么,如果要做到线程安全,一定失去一些效率,这个就像是t-34坦克和虎式坦克的对比,一辆虎式在战斗可以单挑十几辆t-34,(见奥托.卡利乌斯的马利诺沃村之战),但是t-34赢得了战争,因为t-34制造简单,数量多,所以赢得了战争,但是造坦克我们不能只追求产量啊,所以我们的t-34在机动性,活力,装甲设计上都做了相当不错的相对于早期坦克的改进。那么我们对synchronized也应该改进一下吧。synchronized现在也是做过更新的,在1.6之后,加入了偏向锁和轻量级级锁,让synchronized不早那么的让人所诟病。对于markword对象头使用可以让我们更加容易的理解synchronized关键字。

    但是,synchronized毕竟还是有些问题是没有解决的,例如读写的问题,其实我们知道的在数据库当中读写的例子就可以发现,读的时候大家都是可以读的,因为毕竟不影响数据的安全吗?但是写的时候就不一样了,什么悲观锁乐观锁啊,CAS,ABA之类的东西让人十分难受,但是很明显的是读写和可以分开的,那么lock提供了一组readwritelock来供我们使用,瞬间对我们的效率提升了一大截,最少是读的效率上面提升了一大截,内存型非关系型的数据库在读的效率明显比关系型数据库要快。在我所看到的所有关于读写锁的应用当中,我认为是关于hashtable和concurrentHashmap之间,为什么会说hashTable要被淘汰,concurrentHashmap既可以做到线程安全,还在效率上面有了明显的提升,合理的是使用volatile和readwritelock,并且将map分为了若干个segment,减小了锁的粒度。

   至于synchronized和lock的区别,这个参考如下;

  

 

1、ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候

     线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,

     如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断

     如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情

 

    ReentrantLock获取锁定与三种方式:
    a)  lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁

    b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;

    c)tryLock(long timeout,TimeUnit unit),   如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;

    d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断

 

2、synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中

 

3、在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;

  在这里先稍微提一下synchronized和ReentrantLock的区别,也提到了ReentrantLock中的读写锁。下一篇打算深入的分析一下ReentrantLock的内部实现,以上都是一些随笔


  






0 0
原创粉丝点击